PHP防御CSRF攻击的完整实现方案

一、CSRF攻击原理与危害

CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全威胁,攻击者诱使用户在已登录的Web应用中执行非预期的操作。典型的CSRF攻击场景包括:

  1. 用户登录银行网站A后未登出
  2. 访问恶意网站B,B中隐藏表单自动提交转账请求
  3. 银行网站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);

五、最佳实践建议

  1. 令牌生成

    • 使用加密安全的随机数生成器(如random_bytes
    • 每个会话使用唯一令牌
    • 重要操作使用一次性令牌
  2. 令牌传输

    • 表单使用隐藏字段
    • AJAX请求放在请求头
    fetch('/api/endpoint', {
      method: 'POST',
      headers: {
        'X-CSRF-Token': '<?php echo $csrfToken; ?>'
      }
    });
    
  3. 安全增强

    • 设置SameSite Cookie属性
    • 重要操作添加二次验证
    • 敏感操作使用验证码
  4. 性能考虑

    • 令牌缓存而非每次生成
    • 静态资源免验证

六、常见问题解决方案

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?

方案

  1. 使用OAuth2.0授权
  2. 校验Origin/Referer头
  3. 使用自定义请求头

七、安全测试验证

  1. 使用Burp Suite测试重复提交
  2. 尝试跨域伪造请求
  3. 检查令牌是否一次性有效
  4. 验证Cookie的SameSite设置

标签: PHP安全, CSRF, Web安全, 安全编程

添加新评论