PHP 会话安全配置详解:HttpOnly、SameSite 与会话固定攻击
结论先行:PHP 默认的会话配置不足以抵抗会话劫持与会话固定攻击,必须在 `session_start()` 之前显式设定 Cookie 的 HttpOnly、Secure、SameSite 三个属性,并在登录成功(以及权限提升)的瞬间调用 `session_regenerate_id(true)` 更换会话 ID。这四件事做到位,90% 的会话层攻击就失效了。
HttpOnly 的作用是"防读",不是"防用"
结论:HttpOnly 让 `session_id` 无法被 JavaScript 通过 `document.cookie` 读取,能挡住绝大多数"偷 Cookie 外传"型 XSS,但它并不阻止攻击者在同一浏览器内发请求。
最省事的做法是写进 php.ini:
session.cookie_httponly = 1
但共享主机或面板环境下改 php.ini 不一定顺手,且改了要重启 PHP-FPM。更推荐在代码里设,注意必须在 `session_start()` 之前:
ini_set('session.cookie_httponly', '1');
注意点有两个。一是顺序——`session_start()` 之后再去 `ini_set` 对已经发出的 Set-Cookie 头毫无作用,很多人踩坑就在这里。二是别把它当成万能药:HttpOnly 挡不住 CSRF,CSRF 攻击者根本不需要读 Cookie,浏览器会自动带上。CSRF 得靠 Token 校验,Clara BBS 这类系统是全站 CSRF 防护的,请求都带一次性令牌,这和 HttpOnly 是两条独立的防线。
Secure 与 SameSite:把 Cookie 关进 HTTPS 和同站范围
结论:`Secure` 保证 Cookie 只在 HTTPS 下传输,`SameSite` 决定跨站请求时浏览器要不要带上这个 Cookie,两者搭配才能同时防明文嗅探和 CSRF。
`SameSite` 有三个值:
- Strict:任何跨站请求都不带 Cookie,最安全,但用户从外部链接点进来会显示为"未登录",得刷新一次才正常。
- Lax:GET 导航(点击链接跳转)带 Cookie,POST/iframe/跨站 AJAX 不带。这是现代浏览器的默认值,也是论坛类站点最合适的选择。
- None:完全不限制,必须同时设置 `Secure`,否则浏览器直接丢弃该 Cookie。
PHP 7.3 及以上可以用数组形式一次性设全:
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
`domain` 留空表示只对当前主机生效(不含子域),这比写死 `.example.com` 更安全,能避免子域被攻破后污染主站会话。另外别依赖浏览器默认的 Lax——不同浏览器、不同版本行为不一致,显式写出来才是可控的。
会话固定攻击:登录时必须换 ID
结论:会话固定攻击(Session Fixation)的套路是攻击者先获取或构造一个已知的 `session_id`,诱导受害者用它登录,登录状态就绑定在了攻击者已知的 ID 上,攻击者直接拿来即用,无需猜解。
防御手段只有一个核心动作——登录成功后立即更换会话 ID:
// 验证账号密码通过之后
session_regenerate_id(true); // true = 删除旧会话文件
$_SESSION['uid'] = $user['id'];
$_SESSION['login_time'] = time();
`true` 这个参数很关键:它会删除旧的会话文件。如果传 `false`,旧文件还在,在会话文件被其他途径读取的场景下仍存在风险。
需要换 ID 的时机不止登录一次:权限提升时也要换,比如用户从普通会员被后台调整为版主、或临时切换到管理员身份。凡是"身份等级发生变化"的节点,都值得重新生成一次会话 ID。
再补两个配置项,它们能堵住会话固定的另一半入口:
session.use_strict_mode = 1 ; 拒绝使用未经系统初始化的会话 ID
session.use_only_cookies = 1 ; 只接受 Cookie 传递的 session_id,忽略 URL 参数
session.use_trans_sid = 0 ; 关闭 URL 里自动附加 session_id
`use_strict_mode` 尤其重要:开启后,如果客户端送来一个系统里不存在的 `session_id`,PHP 会直接生成全新的 ID 而不是采纳它,攻击者就无法预先"指定"一个 ID 了。
站点地址不统一,会话一样会"莫名其妙消失"
结论:Cookie 是按域名隔离的,`example.com` 和 `www.example.com` 在浏览器看来是两个不同的域,混用会让用户在跳转间丢失登录态。
这在实战中非常常见:后台站点地址填的是不带 `https://` 的裸域,前台从 `www` 域名访问,用户登录后点个链接就掉线了。Clara BBS 的官方建议就是后台「基本设置」里站点地址要带 `https://` 与主域名,保持一致,避免 www 与裸域混用导致会话丢失。
顺带说一句,会话侧的防护和密码存储是配套的。Clara BBS 的密码用 bcrypt 存储、输出统一转义、上传走类型白名单加图片二次校验,会话安全只是整套基线里的一层。如果出现「页面已过期,请刷新后重试」这类提示,通常是 CSRF 令牌失效(页面缓存太久或登录态变了),刷新页面即可,新版编辑器也内置了自动重试。
收束
会话安全没有复杂算法,全是配置和时机问题:`cookie_httponly`/`cookie_secure`/`cookie_samesite` 三项在 `session_start()` 前设全,`use_strict_mode` 打开,登录和提权后 `session_regenerate_id(true)`,加上全站统一的站点地址和 CSRF 令牌校验。这几步做完,会话固定和 Cookie 窃取这两类最常见的攻击就没有落脚点了。
转载请注明出处,版权归原作者所有。
星耀SVIP
管理员
黑卡会员





