登录成功后必须立即调用session_regenerate_id(true),在密码验证通过、写入敏感session数据后且重定向前执行,禁用URL传参、启用Strict Mode和HttpOnly/Secure Cookie,并绑定UA与IP上下文。

登录成功后必须立刻调用 session_regenerate_id(true)
不执行这一步,等于把刚生成的登录态直接交给攻击者。会话固定攻击的核心就是“登录前后 ID 不变”,而 PHP 默认绝不会自动更换 ID。
关键点在于:true 参数不可省略——它强制删除旧会话文件;若只写 session_regenerate_id(),旧会话数据仍可能残留,攻击者用原 ID 仍能读取刚写入的 $_SESSION['user_id']。
- 必须在密码验证通过、数据库查出有效用户之后,且在写入敏感 session 数据(如
$_SESSION['user_id'])之后、重定向(header('Location: ...'))之前执行 - 不能在
session_start()后立即调用,也不能在登录失败时调用(否则会暴露新 ID 给未授权用户) - 若使用 Redis 或 Memcached 自定义 session 处理器,需确认其
destroy()方法被session_regenerate_id(true)正确触发
登录页本身不该提前分配会话 ID
很多项目一打开 login.php 就无条件执行 session_start(),这等于帮攻击者完成了第一步:让受害者还没输入账号密码,就已经被绑定了一个可预测、可传递的 PHPSESSID。
真实场景中,攻击者只需发一个带 ?PHPSESSID=abc123 的链接,等用户点击并登录,这个 ID 就立刻绑定身份。
立即学习“PHP免费学习笔记(深入)”;
- 未登录态页面尽量避免启动会话;真需要存 CSRF Token 或错误计数,应在登录前先
session_destroy()+session_unset(),再session_start()生成干净会话 - 主动拦截手动传参的会话 ID:
if (isset($_GET['PHPSESSID'])) { session_destroy(); exit; }—— 开发时方便,上线必须关 - 禁用透明传输:
ini_set('session.use_trans_sid', '0'),防止 ID 泄露在 URL 中
Cookie 属性与会话配置必须同步收紧
只换 ID 不够。旧 Cookie 还在浏览器里,会自动发送;如果没设 Secure 和 HttpOnly,XSS 脚本就能偷走它,或者明文 HTTP 下被中间人截获。
session_set_cookie_params() 必须在 session_start() 之前调用,顺序错了就失效。
-
session.cookie_httponly = 1:阻止 JavaScript 访问 Cookie,防 XSS 窃取 -
session.cookie_secure = 1:仅 HTTPS 传输,生产环境必须启用 -
session.cookie_samesite = 'Lax'或'Strict':防 CSRF,PHP 7.3+ 支持字符串值,低版本别写数组 -
session.use_strict_mode = 1:拒绝未知 ID 初始化,防止攻击者伪造?PHPSESSID=xxx强行关联会话 -
session.use_only_cookies = 1:彻底禁用 URL 传参式会话,和use_trans_sid形成双重保险
会话上下文绑定不是可选项,而是高敏操作前提
单靠 ID 更换和 Cookie 属性,无法防御已拿到合法 ID 的攻击者。一旦 ID 泄露(比如从日志、代理、Wireshark 中被截获),后续所有请求都默认可信——除非你额外校验上下文。
这不是“加一层更安全”的锦上添花,而是对转账、改密、登出等操作的事实性准入门槛。
- 登录成功后存入:
$_SESSION['ua_hash'] = hash('sha256', $_SERVER['HTTP_USER_AGENT'] ?? ''); $_SESSION['ip'] = $_SERVER['REMOTE_ADDR']; - 每次敏感操作前校验:
if ($_SESSION['ua_hash'] !== hash('sha256', $_SERVER['HTTP_USER_AGENT'] ?? '')) { /* 拦截 */ } - 注意 NAT 场景:IP 校验建议放宽到 C 段(
ip2long($ip) & 0xFFFFFF00),或结合 UA + 时间窗口做组合判断 - 不要只在校验登录态时做一次——攻击者可在登录后换设备、换网络,再发起二次操作
真正容易被忽略的是:会话固定不是孤立漏洞,它几乎总是和会话劫持配合使用。防御必须覆盖整个生命周期——从第一个请求开始,到每一次敏感操作前。



















