PHP $_SESSION['token']跨页面传值时存储值变动问题排查
问题根本原因
核心是你把Token生成、写入Session的逻辑放在了可被重复触发的请求响应流程里,没有做存在性判断,每次请求命中对应PHP脚本时,都会无条件覆盖$_SESSION中已存储的Token值。
具体对应你遇到的现象:
- 你把
session_start()、Token生成、Session赋值、值输出的逻辑全部嵌在了input标签的value属性中,没有做「仅在需要展示表单、且Session内无有效Token时才生成新值」的判断。 - 你看到时间戳部分完全一致、仅随机数不同,是因为覆盖Session值的额外请求和你首次打开表单的请求发生在同一秒内:除了用户主动加载表单的主请求,浏览器自动发起的favicon请求、页面空src属性触发的同域请求、浏览器预加载请求等,只要这些请求被当前PHP脚本处理,就会重新执行一次Token生成赋值的逻辑。同一秒内
time()返回值完全相同,只有rand()生成的数字变化,就会出现你观察到的Session内Token值和输出到表单的值不一致的问题。 - 更换
random_bytes()、openssl_random_pseudo_bytes()仍然出问题的原因也很简单:不管用什么随机数生成函数,只要请求进来就无条件覆盖Session内的Token,值必然会发生变动,和随机函数本身没有关系。
额外提一句:session_start()必须在所有HTTP响应内容输出之前调用,你把它写在HTML input标签里属于错误用法,很容易触发"Headers already sent"类的报错。
修复方案
按以下逻辑调整代码即可:
- 将
session_start()移到PHP脚本的最顶部,保证在任何HTML输出、echo输出之前调用,不要嵌在HTML标签内部。 - 生成Token时增加存在性判断:只有当
$_SESSION中不存在有效Token时,才生成新的Token写入Session,禁止每次请求都无条件覆盖Session值。 - 渲染表单时,直接读取Session中已经存储好的Token输出到input的value属性中,不要在渲染输出阶段重新生成Token。
- 提交后的校验环节使用
hash_equals()做字符串比对,避免时序攻击,校验完成后可删除Session内的Token,实现一次性Token防护,避免表单重复提交。
参考实现代码
表单页代码:
<?php // 脚本最开头启动会话,所有输出前执行 session_start(); // 仅Session中无有效Token时才生成新值 if (!isset($_SESSION['token'])) { // 用安全的随机函数生成Token,不要用time()+rand的低熵组合 $_SESSION['token'] = bin2hex(random_bytes(16)); } ?> <form action="submit_target.php" method="post"> <!-- 直接输出Session中已存的Token,做html转义防止XSS --> <input type="hidden" name="token" value="<?php echo htmlspecialchars($_SESSION['token']); ?>"> <!-- 其他业务表单字段 --> <input type="text" name="username"> <button type="submit">提交</button> </form>
提交处理页代码:
<?php session_start(); $tokenValid = false; if (isset($_POST['token'], $_SESSION['token'])) { // 安全比对,防止时序攻击 $tokenValid = hash_equals($_SESSION['token'], $_POST['token']); // 校验完成即销毁Session内的Token,实现一次性使用 unset($_SESSION['token']); } if (!$tokenValid) { exit("非法请求,Token校验失败"); } // 后续执行业务逻辑
注意:不要使用
time() . "_" . rand(1,100)的格式生成防护用Token,该组合的随机熵极低,攻击者最多尝试100次即可猜中合法Token,完全无法起到CSRF防护作用。
内容的提问来源于stack exchange,提问作者Roberto R
相关产品推荐
相关产品推荐

