PHP跨站请求伪造怎么防

wen PHP项目 1

PHP跨站请求伪造(CSRF)防护实战指南

目录导读

  1. CSRF攻击的本质与危害 - 理解攻击者如何利用你的身份
  2. PHP中CSRF漏洞的常见成因 - 为什么你的代码容易中招
  3. 终极防护方案:Token同步器模式详解 - 最可靠的双重验证机制
  4. 进阶防护策略:SameSite Cookie与请求头校验 - 纵深防御体系
  5. 框架级防护(Laravel/ThinkPHP) - 避免重复造轮子
  6. 常见误区与绕过技巧 - 攻击者的视角
  7. 安全问答环节 - 开发者高频疑问解答

CSRF攻击的本质与危害

跨站请求伪造(Cross-Site Request Forgery)是一种利用用户已认证身份,在用户不知情的情况下,代其执行非本意操作的攻击手段。

PHP跨站请求伪造怎么防

想象这个场景:用户登录了银行网站,cookie未过期,攻击者在论坛发布一张图片,其src指向银行转账接口,用户浏览论坛时,浏览器自动发起对银行接口的HTTP请求,携带着有效的会话cookie,转账指令被执行。整个过程中,攻击者完全不需要获取用户的cookie,只需要一个"诱饵请求"即可。

危害等级:,可导致资金损失、信息泄露、账号篡改、甚至服务器被控制。


PHP中CSRF漏洞的常见成因

  1. 仅依赖cookie判断身份 - 没有二次校验请求来源。
  2. GET请求执行写操作 - 如/delete.php?id=1,极易被img标签触发。
  3. 未使用一次性令牌 - 或者令牌可预测(如时间戳)。
  4. 跨域请求未做Origin/Referer检查

终极防护方案:Token同步器模式(核心)

这是目前公认最稳固的PHP原生防护手段,核心思想:服务器生成随机令牌,嵌在表单中;提交时校验令牌是否匹配且未过期。

步骤1:生成与存储Token

// 启动会话
session_start();
// 生成新的CSRF令牌(若不存在)
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

步骤2:表单中嵌入隐藏字段

<form method="POST" action="transfer.php">
    <input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
    <input type="text" name="amount">
    <button type="submit">转账</button>
</form>

步骤3:服务端严格校验

// 校验请求方法必须是POST(针对关键操作)
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    die('Invalid request method');
}
// 校验令牌是否存在且匹配
if (!isset($_POST['csrf_token']) || 
    !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
    http_response_code(403);
    die('CSRF token validation failed');
}
// 可选:一次性令牌(使用后立即销毁,防止重放)
unset($_SESSION['csrf_token']);

关键细节:使用hash_equals()函数比较字符串,这不是普通,它能防时序攻击,令牌必须用random_bytes()生成,绝不能使用mt_rand()time()


进阶防护策略:纵深防御

仅靠Token还不够,建议叠加以下措施:

SameSite Cookie属性(现代浏览器强力防御)

setcookie('session', $sessionId, [
    'httponly' => true,
    'samesite' => 'Lax', // 或 'Strict'
    'secure'   => true,  // 强制HTTPS
]);
  • Lax模式:允许顶层导航GET请求携带cookie,但阻止跨站POST。
  • Strict模式:完全阻止跨站携带cookie,但用户体验稍差。

校验Origin/Referer头(低成本辅助)

$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
$allowedHost = parse_url(APP_URL, PHP_URL_HOST);
$originHost = parse_url($origin, PHP_URL_HOST);
if ($origin && $originHost !== $allowedHost) {
    die('Cross-origin request blocked');
}

注意:Referer可能被浏览器禁用,因此只能作为辅助,不能替代Token。

双重提交Cookie(适用于前后端分离)

在请求头中携带自定义X-CSRF-Token字段,同时该值也存储在cookie中,服务端校验二者是否一致,适用于API接口。


框架级防护(Laravel/ThinkPHP)

主流PHP框架已内置CSRF防护,务必开启:

Laravel

  • 全局中间件VerifyCsrfToken自动验证。
  • 在Blade模板中:@csrf指令自动生成隐藏字段。
  • 对AJAX请求:在meta标签中写入token,并通过fetchaxios携带。

ThinkPHP 6

  • 主站默认开启middleware中的checkCrsf
  • 表单中添加{:token()}(新版本为{:token_field()})。
  • 检查配置文件中'token_on' => true

强烈建议:直接使用框架的防护,不要自行实现(除非你非常清楚每个安全边界)。


常见误区与绕过技巧(攻击者视角)

常见错误实现 攻击者的绕过方式
Token放在URL参数中 浏览器历史、Referer泄露
Token不绑定用户会话(全局单token) 任何用户可互换token
用GET请求提交表单 img标签、iframe触发
未检查请求来源 跨站图片加载即可触发
令牌过期时间过长(>30分钟) 攻击窗口增大
$_REQUEST做校验(包含GET) 通过GET拼token绕过POST限制

经典绕过案例:如果服务器接受$_REQUEST['token']且允许GET发起操作,攻击者构造<img src="transfer.php?amount=999&token=已知token">,此时校验可通过(因为从GET中读到了token)。


安全问答环节

问1:CSRF和XSS有什么区别? 答:XSS是注入恶意脚本到受害页面,从而窃取信息或操纵DOM,CSRF是利用用户已认证的身份,借浏览器发送伪造请求。关键区别:CSRF不依赖注入脚本,只需要诱导受害者发起请求。

问2:我的网站是纯API接口(无页面),还需要CSRF防护吗? 答:需要,API接口更应验证请求来源,推荐方案:要求请求头携带X-Requested-With: XMLHttpRequest,并验证Origin头,但最稳妥是请求头中携带token(非cookie)。

问3:Token存储在哪里更安全?Session还是数据库? 答:Session是最常规做法,数据库存储适合多服务器集群(共享会话),Token本身不敏感,泄露后只要不匹配Session即失效,因此Session存储足够安全。

问4:如何防止CSRF Token被窃取? 答:结合HttpOnly Cookie(防止XSS读取)、Secure属性(仅HTTPS传输)、限制Token有效期(建议每次请求后刷新或5-10分钟后失效)。


CSRF攻击虽然老生常谈,但在现代开发中依然高频出现。核心要点总结

  • 所有写操作必须POST + Token验证
  • 使用random_bytes()生成高熵Token
  • hash_equals做恒定时间比较
  • SameSite Cookie作为第二道防线
  • 优先使用成熟框架的防护机制

安全是一条持续对抗的战线,定期审计代码、更新依赖库、保持安全意识,才能守住用户数据的底线,请务必在开发全过程中将CSRF防护内化于设计,而不是事后补救。

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