会话固定攻击(Session Fixation)是一种让攻击者预先设定受害者Session ID,然后诱导受害者使用该ID登录,从而窃取用户会话权限的攻击手段。最直接有效的防御方法就是在用户登录成功后立即重新生成Session ID,同时配合Cookie安全属性设置、Token绑定和传输层加密,构建多层防护体系。下面我会把这种攻击的原理、危害、具体代码实现和最佳实践全部讲透。

一、会话固定攻击到底是怎么回事

正常情况下,用户访问网站时服务器会生成一个唯一的Session ID并通过Cookie发送给浏览器,后续请求都携带这个ID来维持会话。但会话固定攻击打破了这个信任链条。攻击者的操作流程通常是这样的:首先攻击者访问目标网站,获取一个合法的Session ID;然后通过各种手段把这个ID"固定"到受害者的浏览器上,比如发送一个带有Session ID参数的链接、通过XSS注入、或者利用子域名Cookie共享漏洞;等受害者用这个ID登录之后,攻击者就可以用同一个ID直接接管受害者的账号。

这种攻击的核心在于:服务器没有在用户身份状态发生变化(比如登录)时重新验证和更换Session ID。很多老旧系统或者配置不当的框架都存在这个问题,尤其是那些只在首次访问时创建Session、之后不做任何更新的应用。

二、会话固定攻击的常见入侵路径

第一种是URL参数传递Session ID。有些网站为了兼容不支持Cookie的客户端,允许通过URL参数传递Session ID,比如http://example.com/page?PHPSESSID=abc123。攻击者只需要把这个链接发给受害者,受害者点击后浏览器就会使用攻击者指定的Session ID。

第二种是Cookie注入。攻击者通过子域名或者HTTP响应头注入的方式,在受害者浏览器中写入目标域名的Session Cookie。比如目标网站是example.com,攻击者控制了app.example.com,就可以设置example.com域的Cookie。

第三种是通过XSS漏洞写入Cookie。虽然这本质上是XSS攻击,但最终效果是固定了Session ID,所以也归类在会话固定的范畴内。

三、重新生成Session ID的核心代码实现

防御会话固定攻击最关键的一步就是在用户登录成功后销毁旧Session并创建新Session。以下是不同语言和框架的具体实现方式。

PHP原生实现:

// 用户登录验证通过后
session_start();

// 销毁旧的Session
session_destroy();

// 重新创建Session
session_start();

// 重新生成Session ID(PHP 7.0+推荐使用session_regenerate_id)
session_regenerate_id(true);

// 设置Session变量
$_SESSION['user_id'] = $userId;
$_SESSION['login_time'] = time();

Java Servlet实现:

// 登录验证通过后
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
    oldSession.invalidate(); // 销毁旧Session
}

// 创建新Session
HttpSession newSession = request.getSession(true);

// 重新生成Session ID(Tomcat默认在创建新Session时会生成新ID)
String newId = newSession.getId();

// 绑定用户信息
newSession.setAttribute("userId", userId);
newSession.setAttribute("loginTime", System.currentTimeMillis());

Node.js Express + express-session实现:

app.post('/login', (req, res) => {
    // 验证用户名密码...
    if (authenticated) {
        // 销毁旧Session
        if (req.session) {
            req.session.destroy(err => {
                if (err) {
                    return res.status(500).send('Session destroy error');
                }
                // 创建新Session
                req.session.regenerate(err => {
                    if (err) {
                        return res.status(500).send('Session regenerate error');
                    }
                    req.session.userId = userId;
                    req.session.loginTime = Date.now();
                    res.redirect('/dashboard');
                });
            });
        }
    }
});

四、Cookie安全属性的配套设置

光重新生成Session ID还不够,Cookie本身的安全属性也必须配置正确。否则即使Session ID换了,Cookie仍然可能被窃取或篡改。

HttpOnly属性:防止JavaScript通过document.cookie读取Session Cookie,直接阻断XSS窃取Session的路径。

Secure属性:确保Cookie只通过HTTPS传输,避免在HTTP明文传输中被中间人截获。

SameSite属性:设置为Strict或Lax,防止跨站请求携带Cookie,降低CSRF和部分会话固定的风险。

Domain和Path限制:将Cookie的Domain精确设置为当前域名,Path设置为具体路径,避免子域名共享导致的Cookie注入。

PHP设置示例:

session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Strict'
]);

五、Token绑定与多因素验证增强防护

除了重新生成Session ID,还可以采用Token绑定技术。具体做法是在Session中存储一个随机生成的Token,同时在Cookie中也存储这个Token的哈希值。每次请求时服务器比对两个值是否一致。这样即使攻击者获取了Session ID,没有对应的Token也无法使用。

另外,对于高安全需求的系统,建议在登录环节加入多因素认证(MFA)。即使Session被固定,攻击者没有第二因素验证码也无法完成登录。这不是直接防御会话固定,但从整体安全架构上极大提高了攻击门槛。

六、框架层面的自动防护机制

主流Web框架通常都内置了会话固定防护。比如Spring Security默认会在登录成功后自动更换Session ID,Django的session框架在用户认证后也会调用session.cycle_key()来更换Session Key。Laravel框架在登录事件中会自动重新生成Session ID。

但需要注意的是,框架的自动防护往往依赖于正确的配置。如果开发者手动绕过了框架的认证流程,或者使用了不当的Session管理方式,自动防护就会失效。所以理解底层原理比依赖框架更重要。

七、服务器端的额外加固措施

第一,禁止通过URL传递Session ID。在PHP中设置session.use_only_cookies = 1,在Tomcat中配置sessionCookiePath和sessionCookieHttpOnly。

第二,设置合理的Session过期时间。不要让Session长期有效,空闲超时和绝对超时都要设置。建议空闲超时15-30分钟,绝对超时不超过2小时。

第三,监控异常Session行为。如果同一个Session ID在短时间内出现在不同IP、不同User-Agent的请求中,应该触发告警并强制重新认证。

第四,定期轮换Session加密密钥。即使Session ID本身安全,如果加密密钥泄露,所有Session都会被破解。定期更换密钥是必要的运维操作。

八、检测与应急响应

如果怀疑系统已经遭受会话固定攻击,首先要检查日志中是否存在异常的Session创建模式,比如大量Session在短时间内被创建但没有对应的登录行为。其次要立即强制所有在线用户重新登录,批量使现有Session失效。最后要排查攻击入口,修补导致Session ID可被外部设定的漏洞。

从长期来看,建立完善的安全审计机制,定期进行渗透测试,特别是针对会话管理模块的专项测试,是预防此类攻击的根本手段。不要等到出了事故才想起来加固,安全永远是前置的工作。

九、总结与核心要点回顾

会话固定攻击看似简单,但危害极大,因为它直接绕过了身份验证环节。防御的核心就是三板斧:登录后必须重新生成Session ID、Cookie必须设置HttpOnly和Secure等安全属性、传输必须全程HTTPS。在此基础上,Token绑定、IP检测、异常监控都是加分项。作为开发者,不要只依赖框架的默认行为,要深入理解每一行安全代码背后的逻辑,才能真正守住用户的会话安全。