🛡️ 第四章 系统漏洞的防范
📄 一句话概括

用户登录会话劫持(会话劫持)攻击的不是你的密码,而是"你已经登录"这个状态本身——服务器在登录成功后发给你一个会话令牌(Cookie 里的那个随机串),之后它认的是这个令牌,不是你的密码。所以攻击者只要拿到这个令牌,就等于拿到了你的登录状态,连密码都不用知道。

📝 本节和前面几节的关系

4-5 的 XSS 可以"把令牌读走",4-6 的 CSRF 可以"借用令牌发请求"——它们盯的都是同一个东西:会话令牌。这一节把这个东西本身讲清楚。

它也是 第二章 那几节(窃听、中间人)的"收割环节":第二章讲的是"通道被看光了",这一节讲的是"看光之后能直接拿走什么"——答案是:能拿走登录状态,而登录状态比密码更值钱(因为多因子认证也拦不住它)。

一、先说清"会话"这件事的设计

HTTP 协议本身是"无记忆"的:你发的两个请求之间,服务器默认不知道它们是同一个人发的。 所以网站要"记住你已登录",就必须在每次请求里带一个凭证——这就是会话令牌(Session ID)。

登录过程是这样的:

  1. 你提交账号密码;
  2. 服务器验证通过,生成一个随机字符串(令牌),存在服务端,同时把它写进你浏览器的 Cookie;
  3. 之后你每发一个请求,浏览器自动带上这个 Cookie;
  4. 服务器只看这个令牌:"有、且没过期 → 这就是刚才登录的那个人。"

关键就在第 4 步:登录之后,服务器认的是令牌,不是密码。 密码只在"第 2 步"用过一次,之后全程不再参与。

这解释了三件事:

二、令牌在 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 位),比较时用【常量时间比较】防时序侧信道

读这个输出:

四、⚠️ 2026 现状

30 秒实验

今天做两个动作,各 15 秒:

  1. 在电脑上登录一个你常用的网站,然后找"登录设备管理"或"活跃会话"这类设置页(大部分主流网站都有)。看它列出了几台设备、有没有"退出全部设备"这个按钮。这个动作的意义是:你需要知道"如果哪天令牌被偷了,你有没有一个开关能把所有会话一次掐掉"——如果没有,那出事时你只能改密码(而改密码未必能让令牌失效)。
  2. 在手机上做同一个观察:打开"设置 → 账号 → 安全",看有没有"登录记录"或"最近登录"的列表。顺带看一眼列表里有没有你不认识的设备。这一步不是让你现在去排查什么,而是建立一个习惯: "登录状态的可见性"本身就是一个安全能力——能看见、能一键下线,比"以为没事"要安全得多。

这一节要带走的一句是:密码只在一个瞬间被用过,而令牌在你每一次请求里都在被使用——所以保护"登录状态"和保护"密码"是两件不同的事,不能靠一个多因子认证打天下。

常见误区

  1. 错误:我开了多因子认证,就算令牌被偷也没用。——纠正:多因子认证发生在"登录那一步",而会话劫持发生在"登录之后"。令牌是"已经通过全部验证的通行证",攻击者拿着它发请求,服务器看到的就是一个"已经完全登录的用户"——这个过程里不会再触发任何一次多因子认证。 所以"MFA 加了"和"会话令牌被保护好了"是两件独立的事。2026 年那个"绕过双因子认证"的案例,走的正是这条路径(不是破解验证码,而是拿到登录后的凭证)。结论:MFA 要加,但 Session 的存储、轮换、失效、强制下线这一整套也必须做。
  2. 错误:令牌泄露了,我改个密码就好了。——纠正:改密码不会自动作废已经发出去的令牌。服务器里保存的是"这个令牌对应哪个用户、什么时候过期",它并不随时比对密码——所以改密码之后,攻击者的令牌很可能还能继续用(直到它自然过期或者被显式撤销)。 正确的应急动作是"强制注销该用户的全部会话"——实践中的表现就是那句"退出所有设备"。这也解释了 30 秒实验里为什么要你去找那个按钮: 平时它是"设备管理",出事时它就是你的应急开关。
  3. 错误:我的网站用了 HTTPS,令牌就不会被偷。——纠正:HTTPS 保护的是"传输这一段",它挡不住"终端上的泄露"。令牌泄露的常见路径有三条: ① 传输中(这条被 HTTPS 挡住了,前提是 Cookie 设了 Secure);② 页面上的恶意脚本(XSS 读走,靠 HttpOnly 挡);③ 跨站请求被自动带上(CSRF 借走,靠 SameSite 与 Token 挡)。 所以"上了 HTTPS"只解决了三分之一——剩下两条都得靠 Cookie 属性和输出转义。 反过来说,如果 Cookie 没设 Secure,即使在 HTTPS 站点上,浏览器也可能在降级时把它发出去。
  4. 错误:会话劫持和 CSRF 是一回事。——纠正:两者都涉及会话令牌,但方向正好相反。CSRF 是"攻击者不需要持有令牌"——他借你浏览器的自动携带功能发请求,令牌一直在你的浏览器里,他自己手里什么都没有。会话劫持是"攻击者拿到了令牌的副本"——他可以在自己的机器上,用这个令牌像你一样操作,完全不需要你的浏览器参与。 这个区别决定了防御的重点: CSRF 靠"来源校验与 Token",会话劫持靠"令牌本身的保护(HttpOnly/Secure/轮换)"。 两个防御体系互相独立,不能用一个替代另一个。
自测三问(点开对答案) 1. 为什么说"会话令牌比密码更值钱"? —— **因为令牌是"已经通过了全部验证的通行证",而密码只是"验证的第一步"**。**攻击者拿着密码去登录,还要面对多因子认证、异常登录检测、风控等一道道关卡;拿着令牌去请求,服务器直接就认了**。**更关键的是时效性**:**密码是长期有效的(改了才变),而令牌是短期有效的**——**所以令牌被盗后的窗口虽然短,但那是一段"完全无阻拦"的窗口。** **这也是为什么"多因子认证"不能替代"会话保护":** **它把"拿到密码就能登录"这条路堵上了,但"拿到令牌就能操作"这条路还开着。** 2. Cookie 的四个属性里,为什么说它们是"互不替代"的? —— **因为它们处理的是三条不同的泄露通道**。**HttpOnly 拦的是"页面上的脚本读到令牌"(XSS 的后果);Secure 拦的是"令牌在明文信道上被看到"(中间人窃听);SameSite 拦的是"令牌被跨站请求顺带带出去"(CSRF)**。**这三条通道在技术上是独立的**——**设了 HttpOnly 并不会让令牌在明文信道上更安全,设了 Secure 也不会阻止页面脚本读取它。** **所以"我设了 HttpOnly"这句话本身不构成安全保障**——**必须问的是"我担心的是哪一类攻击,对应的那个属性设了没有"。** **这也解释了 Demo 里那张表为什么要摆成"四种配置 × 三类攻击"的样子:** **看一个配置强不强,要看它的漏在哪一列。** 3. "强制注销全部会话"和"改密码",为什么前者才是正确的应急动作? —— **因为服务器认的是令牌,不是密码**。**令牌一旦签发,它就作为一条有效记录存在服务端;改密码只会更新"密码"这个字段,并不会去检查"有没有旧令牌还挂着"。** **而"强制注销全部会话"做的事正好相反:它把该用户名下所有有效的令牌一次性作废**——**这才是真正切断了攻击者的访问。** **顺带说明一个相关的好设计**:**很多系统提供"修改密码时自动注销其他会话"这个选项**,**它把这两件事合到了一起**——**如果没有这个选项,就要手动去找"退出所有设备"。** **这也是本节 30 秒实验让你提前找一遍那个按钮的原因:** **出事的时候现找,会很被动。**

相关笔记