用户登录会话劫持(会话劫持)攻击的不是你的密码,而是"你已经登录"这个状态本身——服务器在登录成功后发给你一个会话令牌(Cookie 里的那个随机串),之后它认的是这个令牌,不是你的密码。所以攻击者只要拿到这个令牌,就等于拿到了你的登录状态,连密码都不用知道。
4-5 的 XSS 可以"把令牌读走",4-6 的 CSRF 可以"借用令牌发请求"——它们盯的都是同一个东西:会话令牌。这一节把这个东西本身讲清楚。
它也是 第二章 那几节(窃听、中间人)的"收割环节":第二章讲的是"通道被看光了",这一节讲的是"看光之后能直接拿走什么"——答案是:能拿走登录状态,而登录状态比密码更值钱(因为多因子认证也拦不住它)。
一、先说清"会话"这件事的设计
HTTP 协议本身是"无记忆"的:你发的两个请求之间,服务器默认不知道它们是同一个人发的。 所以网站要"记住你已登录",就必须在每次请求里带一个凭证——这就是会话令牌(Session ID)。
登录过程是这样的:
- 你提交账号密码;
- 服务器验证通过,生成一个随机字符串(令牌),存在服务端,同时把它写进你浏览器的 Cookie;
- 之后你每发一个请求,浏览器自动带上这个 Cookie;
- 服务器只看这个令牌:"有、且没过期 → 这就是刚才登录的那个人。"
关键就在第 4 步:登录之后,服务器认的是令牌,不是密码。 密码只在"第 2 步"用过一次,之后全程不再参与。
这解释了三件事:
- 为什么攻击者偷令牌比偷密码更划算:令牌是"已经通过了验证的通行证",不需要再过任何一道关(包括多因子认证——因为多因子认证发生在第 2 步,而劫持发生在第 4 步);
- 为什么改了密码也救不回来:改密码不会自动作废已经发出去的令牌(除非服务端主动做"强制注销全部会话");
- 为什么"登录状态"值得被单独当成一个攻击目标:它是整个系统里权限最高、时效最短、也最容易被忽视的凭证。
二、令牌在 Cookie 里,所以防线在 Cookie 的属性上
令牌通常放在 Cookie 里,而 Cookie 有四个属性可以决定"它在什么条件下会被使用"。这四个属性各自挡住一类攻击,而且互不替代:
| 属性 | 挡什么 | 不设的后果 |
|---|---|---|
| HttpOnly | JS 读取 Cookie | XSS 一发作就把会话令牌抄走 |
| Secure | 明文信道 | 公共 Wi-Fi 上被中间人直接读到 |
| SameSite | 跨站自动携带 | CSRF 有了可乘之机 |
| maxAge | 长期有效 | 令牌泄露后能用很久 |
"互不替代"这句话是这一节的核心:HttpOnly 防的是"被读走",SameSite 防的是"被借用",Secure 防的是"在路上被看见"——它们处理的是三个不同的通道,缺一个就漏一个。
三、动手 Demo:四个属性,各自挡住谁
下面这段脚本把"四种配置"和"三类攻击"放在一起,看谁挡住了谁:
# 三种配置能挡住的攻击(True = 挡住)
configs = [
("只设 Secure", {"XSS 偷令牌": False, "中间人窃听": True, "CSRF": False}),
("只设 HttpOnly", {"XSS 偷令牌": True, "中间人窃听": False, "CSRF": False}),
("只设 SameSite", {"XSS 偷令牌": False, "中间人窃听": False, "CSRF": True}),
("四件套齐全", {"XSS 偷令牌": True, "中间人窃听": True, "CSRF": True}),
]
# 逐行打印:每个配置挡住了哪几类(打勾)/漏了哪几类(留空)
实跑输出:
一、四个属性,缺一个漏一个
属性 挡什么 不设的后果
----------------------------------------------------------------------------------------------
HttpOnly JS 读取 Cookie XSS 一发作就把会话令牌抄走
Secure 明文信道 公共 Wi-Fi 上被中间人直接读到
SameSite 跨站自动携带 CSRF 有了可乘之机
maxAge 长期有效 令牌泄露后能用很久
二、四种配置 × 三类攻击(谁挡住了谁)
配置 XSS 偷令牌 中间人窃听 CSRF
----------------------------------------------------------------------------------------------
只设 Secure 挡不住 挡住 挡不住
只设 HttpOnly 挡住 挡不住 挡不住
只设 SameSite=Lax 挡不住 挡不住 挡住
四件套齐全 挡住 挡住 挡住
→ 关键:三个属性【互不替代】。HttpOnly 帮不上 CSRF,
SameSite 帮不上 XSS —— 必须同时设上。
三、为什么改了密码也救不回来
① 会话令牌是【已经发出去的通行证】,改密码不会自动作废它
② 正确动作是【服务端强制注销全部会话】,而不只是改口令
③ 令牌本身要够随机(256 位),比较时用【常量时间比较】防时序侧信道
读这个输出:
- 第一张表是"属性到攻击"的对照:每一个属性都只解决一个特定通道的问题。这四个属性加起来才凑成一套完整配置——少哪个,就对应少一条防线。
- 第二张表里的对角线最有说服力:"只设 Secure"能挡住中间人窃听、但挡不住 XSS 偷令牌;"只设 HttpOnly"能挡住 XSS 偷令牌、但挡不住中间人窃听。 这说明:单单"设了某个属性"并不等于安全,得看设的是不是"你担心的那类攻击对应的那个属性"。
- 第三部分讲的是一个很实际的问题:为什么"发现令牌泄露了"之后,改密码不一定有用。 因为令牌是"已经发出去的通行证"——服务器保存的是"这个令牌对应哪个用户",而不是"这个用户当前的密码是什么"(改密码不会自动让令牌失效,除非服务端显式做"注销该用户全部会话")。所以正确的应急动作是"强制注销全部会话",而不是"改密码"。
- 第三部分最后一条容易被略过,但很实在:令牌要够随机(这个例子按 256 位算),比较时要用常量时间比较。随机性不够的令牌可以被猜出来;比较时耗时随字符位置变化的写法可以被"时序侧信道"利用——这两个都是实现层面的细节,但都属于"看起来没问题、实际上有问题"的那一类。
四、⚠️ 2026 现状
- 【"多因子认证"不能保护会话令牌,这个认知仍然是最需要普及的一条】。 多因子认证保护的是"登录这一步",而会话劫持打的是"登录之后"。2026 年那份"由 AI 生成的零日利用"里,一个明确提到的特点就是"它绕过了双因子认证"(4-3)——绕过的方式不是"破解了验证码",而是"拿到了登录后的凭证"。所以"MFA 加上了"不等于"账号不会被冒用",还必须有"令牌怎么存、怎么失效、异常时怎么强制下线"这一整套。
- 【"登录状态"在被当成产品能力来做】。 2026 年可以观察到一个明显的趋势:会话管理从"框架的默认行为"变成了"需要配置和审计的功能"——典型表现包括:会话令牌的轮换(登录时换新令牌)、绑定设备指纹或 IP 变化检测、异地登录告警、一键"登出所有设备"、把会话列表展示给用户看("你当前在 3 台设备上登录")。这些都是把"令牌泄露后能做什么"这个窗口压小的做法。
- 【Cookie 四属性在浏览器上已经是"能设就该设"的默认要求】。 现代浏览器对"没有 Secure 的 Cookie 在 HTTPS 页面上的处理"越来越严,对没写 SameSite 的默认按 Lax(4-6)。所以今天再看"会话令牌有没有被好好保护",判断方式很直接: 打开开发者工具看那几个属性有没有勾上——这一步任何人都能做,不需要看代码。
- 【令牌泄露最现实的来源,不是"被猜出来",而是"被第三方代码带走"】。 2026 年的插件生态数据里,XSS 是插件漏洞里数量最多的一类(4-12)。这意味着"页面里跑了别人的 JavaScript"这件事本身就是令牌泄露的通道——前端引入的每一个第三方脚本(统计、客服插件、广告、"一键登录"),都运行在你页面的上下文里,都能读到页面上的东西。 对策除了 HttpOnly,还有更彻底的一层:内容安全策略(CSP)限制"只允许哪些来源的脚本运行"。
30 秒实验
今天做两个动作,各 15 秒:
- 在电脑上登录一个你常用的网站,然后找"登录设备管理"或"活跃会话"这类设置页(大部分主流网站都有)。看它列出了几台设备、有没有"退出全部设备"这个按钮。这个动作的意义是:你需要知道"如果哪天令牌被偷了,你有没有一个开关能把所有会话一次掐掉"——如果没有,那出事时你只能改密码(而改密码未必能让令牌失效)。
- 在手机上做同一个观察:打开"设置 → 账号 → 安全",看有没有"登录记录"或"最近登录"的列表。顺带看一眼列表里有没有你不认识的设备。这一步不是让你现在去排查什么,而是建立一个习惯: "登录状态的可见性"本身就是一个安全能力——能看见、能一键下线,比"以为没事"要安全得多。
这一节要带走的一句是:密码只在一个瞬间被用过,而令牌在你每一次请求里都在被使用——所以保护"登录状态"和保护"密码"是两件不同的事,不能靠一个多因子认证打天下。
常见误区
- 错误:我开了多因子认证,就算令牌被偷也没用。——纠正:多因子认证发生在"登录那一步",而会话劫持发生在"登录之后"。令牌是"已经通过全部验证的通行证",攻击者拿着它发请求,服务器看到的就是一个"已经完全登录的用户"——这个过程里不会再触发任何一次多因子认证。 所以"MFA 加了"和"会话令牌被保护好了"是两件独立的事。2026 年那个"绕过双因子认证"的案例,走的正是这条路径(不是破解验证码,而是拿到登录后的凭证)。结论:MFA 要加,但 Session 的存储、轮换、失效、强制下线这一整套也必须做。
- 错误:令牌泄露了,我改个密码就好了。——纠正:改密码不会自动作废已经发出去的令牌。服务器里保存的是"这个令牌对应哪个用户、什么时候过期",它并不随时比对密码——所以改密码之后,攻击者的令牌很可能还能继续用(直到它自然过期或者被显式撤销)。 正确的应急动作是"强制注销该用户的全部会话"——实践中的表现就是那句"退出所有设备"。这也解释了 30 秒实验里为什么要你去找那个按钮: 平时它是"设备管理",出事时它就是你的应急开关。
- 错误:我的网站用了 HTTPS,令牌就不会被偷。——纠正:HTTPS 保护的是"传输这一段",它挡不住"终端上的泄露"。令牌泄露的常见路径有三条: ① 传输中(这条被 HTTPS 挡住了,前提是 Cookie 设了 Secure);② 页面上的恶意脚本(XSS 读走,靠 HttpOnly 挡);③ 跨站请求被自动带上(CSRF 借走,靠 SameSite 与 Token 挡)。 所以"上了 HTTPS"只解决了三分之一——剩下两条都得靠 Cookie 属性和输出转义。 反过来说,如果 Cookie 没设 Secure,即使在 HTTPS 站点上,浏览器也可能在降级时把它发出去。
- 错误:会话劫持和 CSRF 是一回事。——纠正:两者都涉及会话令牌,但方向正好相反。CSRF 是"攻击者不需要持有令牌"——他借你浏览器的自动携带功能发请求,令牌一直在你的浏览器里,他自己手里什么都没有。会话劫持是"攻击者拿到了令牌的副本"——他可以在自己的机器上,用这个令牌像你一样操作,完全不需要你的浏览器参与。 这个区别决定了防御的重点: CSRF 靠"来源校验与 Token",会话劫持靠"令牌本身的保护(HttpOnly/Secure/轮换)"。 两个防御体系互相独立,不能用一个替代另一个。
自测三问(点开对答案)
1. 为什么说"会话令牌比密码更值钱"? —— **因为令牌是"已经通过了全部验证的通行证",而密码只是"验证的第一步"**。**攻击者拿着密码去登录,还要面对多因子认证、异常登录检测、风控等一道道关卡;拿着令牌去请求,服务器直接就认了**。**更关键的是时效性**:**密码是长期有效的(改了才变),而令牌是短期有效的**——**所以令牌被盗后的窗口虽然短,但那是一段"完全无阻拦"的窗口。** **这也是为什么"多因子认证"不能替代"会话保护":** **它把"拿到密码就能登录"这条路堵上了,但"拿到令牌就能操作"这条路还开着。** 2. Cookie 的四个属性里,为什么说它们是"互不替代"的? —— **因为它们处理的是三条不同的泄露通道**。**HttpOnly 拦的是"页面上的脚本读到令牌"(XSS 的后果);Secure 拦的是"令牌在明文信道上被看到"(中间人窃听);SameSite 拦的是"令牌被跨站请求顺带带出去"(CSRF)**。**这三条通道在技术上是独立的**——**设了 HttpOnly 并不会让令牌在明文信道上更安全,设了 Secure 也不会阻止页面脚本读取它。** **所以"我设了 HttpOnly"这句话本身不构成安全保障**——**必须问的是"我担心的是哪一类攻击,对应的那个属性设了没有"。** **这也解释了 Demo 里那张表为什么要摆成"四种配置 × 三类攻击"的样子:** **看一个配置强不强,要看它的漏在哪一列。** 3. "强制注销全部会话"和"改密码",为什么前者才是正确的应急动作? —— **因为服务器认的是令牌,不是密码**。**令牌一旦签发,它就作为一条有效记录存在服务端;改密码只会更新"密码"这个字段,并不会去检查"有没有旧令牌还挂着"。** **而"强制注销全部会话"做的事正好相反:它把该用户名下所有有效的令牌一次性作废**——**这才是真正切断了攻击者的访问。** **顺带说明一个相关的好设计**:**很多系统提供"修改密码时自动注销其他会话"这个选项**,**它把这两件事合到了一起**——**如果没有这个选项,就要手动去找"退出所有设备"。** **这也是本节 30 秒实验让你提前找一遍那个按钮的原因:** **出事的时候现找,会很被动。**- ☐ 下一节(4-8 内存缓冲区溢出的滥用)
相关笔记
- 假冒他人的攻击 —— 上一篇:借用令牌发请求
- 内存缓冲区溢出的滥用 —— 下一篇:缓冲区溢出式攻击
- 跨越多个网站的攻击 —— XSS 是偷令牌的那条路
- 窃取信息的软件 —— 令牌也是"要偷的信息"之一
- 🛡️ 网络与信息安全 · 索引 —— 全课路线图