PHP项目如何防止CSRF攻击?

wen java案例 3

本文目录导读:

PHP项目如何防止CSRF攻击?

  1. 核心方案:CSRF Token
  2. 进阶与辅助方案
  3. 需要避免的做法
  4. 企业级防御清单
  5. 完整示例(简易框架风格)

在PHP项目中防止CSRF(跨站请求伪造)攻击,最核心、最有效的做法是使用CSRF Token,结合其他辅助手段可以构建更坚固的防御体系。

以下是具体的实现方案和最佳实践:

核心方案:CSRF Token

这是对抗CSRF的行业标准方案,核心思想是:服务器生成一个随机且唯一的Token,嵌入到表单或请求中;当用户提交请求时,服务器验证该Token是否合法。

生成和存储Token

在用户会话(Session)中生成并存储一个安全的随机Token。

<?php
session_start();
// 生成CSRF Token(如果不存在)
if (empty($_SESSION['csrf_token'])) {
    // 使用 random_bytes 生成更安全的随机数
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>

在表单中嵌入Token

在HTML表单中添加一个隐藏字段,值为Session中的Token。

<form method="POST" action="/process.php">
    <!-- 核心:隐藏的CSRF Token -->
    <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
    <!-- 其他表单字段 -->
    <input type="text" name="username" required>
    <input type="submit" value="提交">
</form>

重要: 使用 htmlspecialchars() 防止XSS攻击导致Token泄露。

验证Token

在处理POST、PUT、DELETE等有副作用的请求时,验证提交的Token是否与Session中的Token一致。

<?php
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // 验证Token存在且匹配
    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        // Token不匹配,请求无效
        die('CSRF验证失败,请求被拒绝。');
    }
    // Token验证通过,继续处理业务逻辑
    // ... 处理表单数据 ...
}
?>

进阶与辅助方案

使用一次性Token(Token+Double Submit Cookie)

对于更敏感的操作(如支付、账户删除),建议使用一次性Token,即验证成功后立即销毁旧Token并生成新Token,这可以防止Token被重放。

// 验证成功后
if ($_POST['csrf_token'] === $_SESSION['csrf_token']) {
    // 处理业务...
    // 立即生成新Token,使旧Token失效
    unset($_SESSION['csrf_token']);
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

检查HTTP Referer / Origin

作为第二道防线,可以检查请求头中的 OriginReferer 字段,确认请求来源于本网站。

$allowed_origins = ['https://www.example.com', 'https://admin.example.com'];
if (isset($_SERVER['HTTP_ORIGIN'])) {
    if (!in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
        die('非法来源的请求。');
    }
}

注意: 不能完全依赖此方法,因为 Referer 可能被浏览器禁用或伪造。

AJAX请求的Token传递

对于SPA或AJAX请求,通常将Token放在请求头中。

前端(jQuery示例):

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
    }
});

后端(PHP):

// 从请求头中获取Token
$token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if ($token !== $_SESSION['csrf_token']) {
    die('CSRF验证失败。');
}

HTML中设置Meta标签:

<meta name="csrf-token" content="<?php echo $_SESSION['csrf_token']; ?>">

需要避免的做法

  1. 不要使用GET请求执行有副作用的操作(如删除、修改),GET请求容易被嵌入图片、链接中。
  2. 不要使用简单的、可预测的Token生成方式(如 md5(uniqid())),应使用 random_bytes()
  3. 不要将Token通过URL传递,容易在Referer中泄露。

企业级防御清单

防御措施 重要性 说明
CSRF Token 必需 核心方案,每个用户会话唯一,不可预测。
一次性Token 强烈推荐 用于敏感操作,阻止Token重放。
SameSite Cookie 重要 设置Cookie的 SameSite 属性为 StrictLax,现代浏览器限制跨站请求携带Cookie。
Referer/Origin检查 辅助 增加一道验证,但不可作为唯一手段。
验证码 可选 对高风险操作(如大额转账)可加入验证码。
HTTP方法限制 基础 只允许正确的HTTP方法(如POST)处理写操作。

完整示例(简易框架风格)

安全类(Security.php)

class CSRF {
    public static function generateToken() {
        if (!isset($_SESSION)) session_start();
        if (empty($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['csrf_token'];
    }
    public static function getTokenField() {
        $token = self::generateToken();
        return '<input type="hidden" name="csrf_token" value="' . htmlspecialchars($token) . '">';
    }
    public static function validateToken($token) {
        if (!isset($_SESSION)) session_start();
        if (!isset($_SESSION['csrf_token'])) return false;
        return hash_equals($_SESSION['csrf_token'], $token); // 使用hash_equals防时序攻击
    }
}

视图中使用(form.php)

<form method="POST">
    <?php echo CSRF::getTokenField(); ?>
    <input type="text" name="data">
    <button type="submit">提交</button>
</form>

控制器中验证(process.php)

session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!CSRF::validateToken($_POST['csrf_token'] ?? '')) {
        http_response_code(403);
        exit('CSRF验证失败');
    }
    // 业务逻辑...
}

通过以上方案,你可以有效地防御绝大多数CSRF攻击。没有银弹,但结合Token、SameSite Cookie、Referer检查和正确的HTTP方法,你的PHP项目将具备非常高的安全性。

抱歉,评论功能暂时关闭!