PHP防御CSRF攻击的完整实现方案
PHP防御CSRF攻击的完整实现方案
一、CSRF攻击原理与危害
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全威胁,攻击者诱使用户在已登录的Web应用中执行非预期的操作。典型的CSRF攻击场景包括:
- 用户登录银行网站A后未登出
- 访问恶意网站B,B中隐藏表单自动提交转账请求
- 银行网站A误以为是用户合法操作执行转账
二、基础CSRF防御实现
2.1 令牌(Token)机制实现
生成令牌页面 (index.php)
<?php
session_start();
// 生成高强度CSRF令牌
function generateCsrfToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
$csrfToken = generateCsrfToken();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>CSRF防护演示</title>
</head>
<body>
<form action="process.php" method="post">
<input type="text" name="username" placeholder="用户名">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken, ENT_QUOTES); ?>">
<button type="submit">提交</button>
</form>
</body>
</html>
验证令牌页面 (process.php)
<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
http_response_code(403);
die('CSRF令牌验证失败,请求被拒绝');
}
// 处理合法请求
$username = $_POST['username'] ?? '';
echo "安全处理用户: " . htmlspecialchars($username);
// 使用后销毁令牌(可选)
unset($_SESSION['csrf_token']);
}
三、高级防御方案
3.1 同源检测
// 检查请求来源
$allowedDomains = ['example.com', 'trusted-site.com'];
$referer = parse_url($_SERVER['HTTP_REFERER'] ?? '', PHP_URL_HOST);
if (!in_array($referer, $allowedDomains, true)) {
die('非法请求来源');
}
3.2 双重Cookie验证
// 设置HttpOnly的Cookie
setcookie('csrf_cookie', bin2hex(random_bytes(16)), [
'expires' => time() + 3600,
'path' => '/',
'domain' => 'example.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
]);
// 验证时检查Cookie和表单token是否匹配
if ($_POST['csrf_token'] !== $_COOKIE['csrf_cookie']) {
die('CSRF验证失败');
}
四、框架集成方案
4.1 Laravel CSRF防护
Laravel自动为每个活跃用户会话生成CSRF令牌:
// 表单中自动包含
@csrf
// 或手动获取
<input type="hidden" name="_token" value="{{ csrf_token() }}">
4.2 Symfony表单组件
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
$csrfGenerator = new UriSafeTokenGenerator();
$csrfStorage = new SessionTokenStorage();
$csrfProvider = new DefaultCsrfProvider($csrfGenerator, $csrfStorage);
五、最佳实践建议
-
令牌生成:
- 使用加密安全的随机数生成器(如
random_bytes
) - 每个会话使用唯一令牌
- 重要操作使用一次性令牌
- 使用加密安全的随机数生成器(如
-
令牌传输:
- 表单使用隐藏字段
- AJAX请求放在请求头
fetch('/api/endpoint', { method: 'POST', headers: { 'X-CSRF-Token': '<?php echo $csrfToken; ?>' } });
-
安全增强:
- 设置SameSite Cookie属性
- 重要操作添加二次验证
- 敏感操作使用验证码
-
性能考虑:
- 令牌缓存而非每次生成
- 静态资源免验证
六、常见问题解决方案
Q1: AJAX请求如何传递CSRF令牌?
解决方案:
// 从meta标签获取
let token = document.querySelector('meta[name="csrf-token"]').content;
// 设置AJAX全局头
$.ajaxSetup({
headers: {
'X-CSRF-Token': token
}
});
Q2: 多标签页场景如何处理?
解决方案:
// 生成页面特定令牌而非会话令牌
$pageToken = hash_hmac('sha256', $_SERVER['REQUEST_URI'], $_SESSION['secret']);
Q3: API如何防御CSRF?
方案:
- 使用OAuth2.0授权
- 校验Origin/Referer头
- 使用自定义请求头
七、安全测试验证
- 使用Burp Suite测试重复提交
- 尝试跨域伪造请求
- 检查令牌是否一次性有效
- 验证Cookie的SameSite设置