Session 固定攻击防护

解读

在国内一线/二线互联网公司的 PHP 后端面试中,"Session 固定攻击防护"属于高频安全考点。面试官通常不会只问"怎么防",而是层层递进:

  1. 能否准确说出攻击原理;
  2. 能否给出 PHP 侧可落地的代码或配置;
  3. 能否结合业务场景(登录、支付、sso、多端)权衡利弊;
  4. 能否把 Session 安全放进整体 SDL(安全开发生命周期)。

回答时切忌只背"session_regenerate_id()"。要把"生成新 ID → 销毁旧 ID → 设置 Cookie 属性 → 配合 CSRFToken → 记录审计日志"完整闭环说出来,才能体现资深水平。

知识点

  1. Session 固定攻击原理
    攻击者先获取一个合法的 Session ID(如 http://example.com/?PHPSESSID=abc123),诱导受害者使用此 ID 登录;登录后服务端未更换 ID,导致攻击者直接拥有已认证会话。

  2. 核心防御手段
    a) 登录态跃迁时必须 session_regenerate_id(true) —— 参数 true 表示立即删除旧会话文件,避免旧 ID 可用。
    b) 设置 session.cookie_httponly = 1,阻断 XSS 读取 Cookie。
    c) 设置 session.cookie_secure = 1,强制 HTTPS 传输。
    d) 设置 session.cookie_samesite = "Lax" 或 "Strict",防止跨站 Cookie 发送。
    e) 使用 session.use_strict_mode = 1,拒绝服务端未初始化的 ID。
    f) 给 Session Cookie 绑定用户侧指纹(UA、IP 段、设备号),变更即失效。
    g) 登录后把旧 Session 数据整体迁移到新 ID,避免丢购物车等体验问题。
    h) 记录"登录→换 ID"审计日志,方便风控与溯源。

  3. PHP 版本差异
    PHP7.1 起支持 session_create_id(),可在 regenerate 前预生成,避免并发竞争;PHP7.3 起支持 SameSite 原生写法。面试时主动提及版本兼容性,可加分。

  4. 框架级封装
    Laravel 自带 Illuminate\Session\Middleware\AuthenticateSession,可配置 regenerate;Symfony 在 security.yml 中 always_remember_me 与 token 配置也能触发 regenerate。说明"我们不用重复造轮子,但要读懂源码,防止中间层遗漏"。

  5. 微服务/多终端场景
    移动端常用 JWT + Refresh Token,Session 固定问题转化为"Token 固定"。此时要答"登录后签发新 Token,旧 Token 加入 Redis 黑名单",体现举一反三能力。

答案

"Session 固定攻击的本质是让受害者用攻击者已知的 Session ID 完成登录,从而共享会话。PHP 端的完整防护分四步:

第一步,修改 php.ini 基础配置:
session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Lax"

第二步,在代码层登录成功瞬间调用:
session_regenerate_id(true); // true 强制删除旧会话文件
// 如需兼容 PHP5.6,可先 session_write_close() 再 regenerate

第三步,把用户侧指纹写入 Session,并在每个请求校验:
SESSION[iphash]=substr(md5(_SESSION['ip_hash'] = substr(md5(_SERVER['REMOTE_ADDR'].SERVER[HTTPUSERAGENT]),0,8);if(_SERVER['HTTP_USER_AGENT']), 0, 8); if (_SESSION['ip_hash'] !== substr(md5(SERVER[REMOTEADDR]._SERVER['REMOTE_ADDR']._SERVER['HTTP_USER_AGENT']), 0, 8)) {
session_unset(); session_destroy(); exit('非法环境');
}

第四步,记录审计日志并配合 CSRF 双令牌:
error_log(date('Y-m-d H:i:s').' UID:'.uid.regeneratesession.uid.' regenerate session '.oldId.'->'.session_id());

通过以上闭环,可在国内电商、CMS、SaaS 等高并发场景下,把 Session 固定攻击面降到几乎为零,同时兼顾用户体验和性能。"

拓展思考

  1. 当业务使用多域名 SSO(如 .a.com、.b.com)时,SameSite=Lax 会导致跨站 POST 登录态丢失,如何兼顾安全与单点体验?
    答:主域统一使用 "SameSite=None; Secure",同时把 Session ID 与 CAS Token 双重校验;对非主域请求再附加 JWT 签名,防止 ID 被篡改。

  2. 在 PHP-FPM + Redis 集群 的分布式 Session 方案里,regenerate_id(true) 会触发旧 key 删除,瞬间造成 Redis 主库 DEL 压力,如何优化?
    答:采用"标记删除"策略:旧 Session 只设置 TTL=60s,由 Redis 自然过期;登录后新 ID 立即写入,读请求优先访问新 ID,60s 后旧 ID 自动失效,避免集中 DEL。

  3. 如果公司使用 Serverless(如 Baidu CFC、阿里云 FC),PHP 进程寿命极短,文件型 Session 不可用,如何防止固定攻击?
    答:把 Session 存入云原生 Redis 或 TableStore,启用 use_strict_mode;登录 regenerate 后,旧 ID 写入黑名单有序集合(zset),过期时间=Token 有效期;网关层统一校验黑名单,实现"无状态 + 高安全"。