最可靠的PHP CSRF防护是用session_regenerate_id(true)重置会话并存token于$_SESSION['csrf_token'],生成用bin2hex(random_bytes(32)),表单以htmlspecialchars处理的hidden字段传递,校验用hash_equals(),失效时静默拒绝。

PHP 中用 session_regenerate_id() 配合 $_SESSION 存 token 最可靠
CSRF 的核心是攻击者能预测或复用你的请求凭证,所以关键不是“生成 token”,而是让 token 具备会话绑定、一次性、时效性。PHP 原生不自带 CSRF token 函数,得自己搭骨架。session_regenerate_id() 不只是防 session fixation,它重置 session ID 的同时,也帮你清掉旧会话里可能被窃取的 token 状态。
- 每次用户登录或权限变更后必须调用
session_regenerate_id(true)(true表示删除旧 session 文件) - token 存进
$_SESSION['csrf_token'],别存 cookie 或前端 localStorage - 生成用
bin2hex(random_bytes(32)),别用md5(time().rand())这种可预测组合 - 表单提交后立即验证并重置:验证通过就
unset($_SESSION['csrf_token']),防止重放
Form 提交时怎么传 token 才不被绕过
很多人把 token 放在 URL 参数里,或者藏在 JS 变量中,结果被 XSS 一抓一个准。真正安全的传法只有一种:作为隐藏字段,且和服务端存储的完全一致。
- 必须用
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token'] ?? ''); ?>">,htmlspecialchars()防止属性注入 - 不要用 JS 动态填充 value,避免 DOM XSS 污染值
- 如果页面有多个 form(比如编辑+删除按钮),每个都得用独立 token,或统一用同一个但校验后立即失效
- AJAX 请求需从 DOM 读取 hidden 字段值,别从全局变量或 API 接口单独取——那等于暴露 token 获取通道
hash_equals() 校验 token 是必须的,不是可选
直接用 == 或 === 比较 token 会触发时序攻击。攻击者靠响应时间差异就能暴力猜出 token 字符,尤其当 token 是 hex 字符串时更明显。
- 校验必须写成
hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '') - 两边都不能为空字符串,否则
hash_equals()会返回false,但你要先确保 key 存在,而不是让它崩在 undefined index - 如果 token 已被 unset,直接拒绝,不给任何提示信息(连 HTTP 状态都别用 403,统一用 400 或重定向到错误页)
CSRF token 在多标签页或长页面中容易失效的几个现实问题
用户开两个 tab 同时操作,或者页面挂了半小时再提交,token 就大概率失效。这不是 bug,是设计使然,但体验要兜住。
立即学习“PHP免费学习笔记(深入)”;
- session 生命周期别设太短(比如
ini_set('session.gc_maxlifetime', 1440)是底线) - 表单页加载时检查
$_SESSION['csrf_token']是否存在,没有就现场生成一个并写入 session,别等提交才报错 - AJAX 表单可加个前置请求
/api/csrf-token刷新 token,但该接口本身也要受 CSRF 保护(比如只允许 POST + referer 白名单) - 别在 JS 里自动重试失败的提交——token 失效后重试只会反复失败,应提示用户刷新页面
最麻烦的其实是前后端分离场景:PHP 只管发 token,但前端可能缓存、复用、跨域丢 header。这时候 token 必须和 session ID 强绑定,且不能依赖客户端传来的 session_id cookie 做校验——得用服务端存储的 session 数据反查,否则换 cookie 就绕过了。



















