📘 第十一章 Web的攻击技术
📄 一句话概括

攻击者先自己领一个会话 ID,想办法让受害者用它登录。 如果登录后服务端不换 ID,那个 ID 就自动变成了「受害者的会话」,而攻击者一开始就拿着它。 Demo 里不换 ID 时攻击者拿 200,一换 ID 就只剩 302。

📝 本节和前面几节的关系

上一节要「偷」,这一节连偷都不用——ID 是攻击者自己领的。

一、三步,一环都不需要技术

第一步:攻击者自己访问一次站点,服务端给了他一个未登录的会话 ID。 第二步:他想办法让受害者的浏览器用上这个 ID。老办法是把 ?SID=xxx 塞进链接里发过去; 有些站点会把 URL 里的会话 ID 写进 cookie,这一步就成了。 第三步:受害者在这个 ID 上完成登录。服务端把这条会话标记成「已登录」。 到这里,攻击者手里那个 ID 自动升级成了「受害者的登录态」——他从头到尾没偷、没猜、没爆破, 只是先到一步。

二、实测:不换 ID 就 200

Demo 第一段是「登录不换 ID」:攻击者领到 8d575eadb2cc8265,受害者用它登录, 服务端回的会话还是这个 ID。 第 4 步攻击者拿最开始那个 ID 去访问 /me,回 200,正文是「看到了 bozai 的账户余额」。 第二段是「登录换新 ID」:受害者登录后 ID 变成新的一个,旧的那个当场被删掉。 攻击者再拿旧的去访问,回 302——被踢回登录页。 两段的差别只有一行代码:登录成功时生成新 ID、作废旧 ID。

三、为什么这条特别值得记

因为它不依赖任何「高深」的攻击手法:不需要窃听、不需要 XSS、不需要破解。 它只依赖一个疏忽——登录前后用同一个 ID。 而修补的成本几乎为零:多生成一串随机数、多删一条记录。 这是安全里少见的「成本接近 0、收益很明确」的修补。 所以它被写进了所有框架的默认行为:登录成功时轮换会话 ID。 如果你手写的登录逻辑里没有这一步,那它就是个洞。 顺带一条相关的:注销时也要删服务端的会话记录,不能只清 cookie。 只清 cookie 的话,那串 ID 在服务端还有效,谁拿到都能继续用。

四、动手 Demo:同一个流程,开不开轮换

两段几乎一样的代码:一段登录时保留原 ID,一段登录时换新 ID 并作废旧 ID。 各跑一遍「攻击者先领 ID → 受害者登录 → 攻击者复用」的全过程。

if path == u'/login':
    if rotate:
        old = sid
        sid = secrets.token_hex(8)
        SESS.pop(old, None)      # 旧 ID 当场作废
    SESS[sid] = {u'user': u'bozai'}
    return 200, [(u'Set-Cookie', u'SID=%s' % sid)]

实跑输出:

--------------------------------------------------------------------
会话固定:ID 是攻击者先挑好的
--------------------------------------------------------------------
  【登录不换 ID】
      1. 攻击者先领一个 ID   1423817118bcd191
      2. 受害者用它登录      登录成功,会话现在是 1423817118bcd191
      3. 登录后服务端给的 ID 1423817118bcd191
      4. 攻击者拿最开始那个 ID 200 看到了 bozai 的账户余额

  【登录换新 ID】
      1. 攻击者先领一个 ID   14559234f7033dbd
      2. 受害者用它登录      登录成功,会话现在是 e8ab244237cf8295
      3. 登录后服务端给的 ID e8ab244237cf8295
      4. 攻击者拿最开始那个 ID 302 

  第一段第 4 行是 **200**,攻击者拿到了余额——他从头到尾没偷任何东西,
  那个 ID 本来就是他领的。这种攻击不需要窃听、不需要 XSS,
  只需要「让受害者的浏览器用上这个 ID」+「登录后 ID 不变」。

  第二段第 4 行是 **302**:登录时换了新 ID,旧的那个当场作废,攻击者手里那个已经没用了。

第一段四步:ID 是 8d575eadb2cc8265,登录后还是它, 攻击者拿它访问得 200 看到了 bozai 的账户余额。 第二段四步:攻击者领到 bb3815d4bb94fe30,受害者登录后 ID 变成 02101816a2ed5c11, 攻击者拿旧的访问得 302。 两段的唯一差别就是那三行 rotate 代码。一个 200 一个 302,一个泄了余额一个被踢回登录页。 8.5.2 也讲过同一件事,但那次是从「防御方」角度讲的; 这次把攻击者的四步完整走了一遍,看清了「他从头到尾什么都没偷」。

30 秒实验

  1. 想想你写过的登录逻辑:登录成功那一步,有没有重新生成会话 ID?
  2. 试试注销之后再用旧 cookie 访问,看服务端认不认。
  3. 想一想:为什么「URL 里带会话 ID」这种做法现在被彻底淘汰了?

常见误区

  1. 「会话 ID 是随机的,攻击者猜不到」——他不用猜,他自己领一个。
  2. 「登录会覆盖旧会话」——不显式作废的话,旧 ID 常常还留着。
  3. 「清掉 cookie 就算退出了」——服务端的记录还在,ID 还能用。
自测三问(点开对答案) 1. 会话固定攻击的三步是什么? —— 攻击者先领 ID,设法让受害者用它登录,登录后 ID 不变就归攻击者了。 2. 最佳的修法是什么? —— 登录成功时生成新 ID,并作废旧 ID。 3. 注销时要做什么? —— 除了清 cookie,还要删服务端的会话记录。

ID 相关的最后一种:让受害者的浏览器「替」攻击者发请求。

相关笔记