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

会话 ID 就是一张不记名的通行证:谁拿着它,服务端就认谁。 Demo 里攻击者换了个完全不同的 User-Agent 重放那个 ID,照样拿到 200。 绑定 UA 能挡住这一种,但代价是会误伤自己人。

📝 本节和前面几节的关系

前三节讲「泄露什么」。这一节讲泄露之后能干什么——会话 ID 一旦被第三方拿到,身份就跟着走了。

一、ID 是不记名的

服务端没法「认出」你,它只认请求里那串 ID。 所以你用什么设备、什么浏览器、从哪来,都不重要——ID 对上就行。 这就是「不记名」的意思:它更像一张纸质门票,不看人,只看票。票被拍照复印了,复印件也能进场。 ID 可能从哪泄露:明文 HTTP 被抓包、URL 里带着被日志和 Referer 记录、XSS 用脚本读 cookie、 共享电脑没清、被社工问出来。 Demo 里第 3 行最直接:攻击者用完全不同的 User-Agent 重放那个 ID, 服务端照样回了 200 欢迎回来,bozai。

二、绑 UA 能挡一点,但不是好办法

一个常见的加固是「把会话绑到某个特征上」,比如绑 User-Agent 或 IP。 Demo 里绑了 UA 之后,本人访问还是 200,攻击者重放变成 401, 回的话是「这个会话不是从这台设备发起的」。 看起来有效,但代价很实在:同一台电脑换个浏览器、浏览器自动升级、 在公司用 Wi-Fi 回家用宽带——都会把自己锁在门外。 绑 IP 更糟:手机切个基站就换 IP 了,移动网络下几乎必然误伤。 所以这一条一般只用在「高敏感操作」上,不当成日常手段。 真正管用的还是那几条:HttpOnly 让脚本偷不到、Secure 只在 HTTPS 发、短过期时间、 敏感操作重新验密码、登录后轮换 ID(11.4.2)。

三、为什么 HttpOnly 这么关键

XSS 偷会话 ID 的标准手法是 document.cookie——一行脚本就能把 cookie 读走发到攻击者的服务器上。 标了 HttpOnly 的 cookie,脚本读不到(浏览器直接不返回),但请求时照样自动带上。 这一条把「XSS → 偷 ID → 长期控制」这条链掐断了。 Secure 是另一条:只在 HTTPS 连接上发送。 它防的是「先让浏览器走一次明文 http,cookie 就漏了」这种降级攻击。 两个属性都要加,而且加起来几乎不花成本——所以这是性价比最高的一处修补。

四、动手 Demo:抄走 ID,换个 UA 重放

本机起一个带会话表的服务端:登录发 ID,/me 要 ID。 先正常登录访问一次,再让「攻击者」拿同一个 ID 但换个 User-Agent 访问, 最后换成「会话绑定 UA」的版本重跑一遍。

if path == u'/login':
    sid = secrets.token_hex(8)
    SESS[sid] = {u'user': u'bozai', u'ua': ua if bind_ua else u'*'}
    return 200, [(u'Set-Cookie', u'SESSIONID=%s; HttpOnly' % sid)]

if bind_ua and SESS[sid][u'ua'] != ua:
    return 401

实跑输出:

--------------------------------------------------------------------
会话劫持:ID 被抄走之后
--------------------------------------------------------------------
  1. 用户登录,拿到会话      c105f173ed96b90e
  2. 本人用这个 ID 访问      200 欢迎回来,bozai
  3. 攻击者抄走 ID 重放      200 欢迎回来,bozai
  4. 攻击者不带 ID           401

  第 3 行是关键:攻击者的 User-Agent 完全不同,服务端照样给了他数据。因为服务端只认那串 ID,不看别的。

  绑了 UA 之后,本人访问     200
  绑了 UA 之后,攻击者重放   401
  攻击者看到的话             这个会话不是从这台设备发起的

  绑定 User-Agent 能挡住这一种重放,但它不是好办法:
  同一台电脑换个浏览器、或者浏览器自动升级,都会把自己锁在门外。
  真正有效的还是那几条:HttpOnly 让脚本偷不到、Secure 只在 HTTPS 发、短过期时间、敏感操作重新验密码。

  日志里出现的请求数         7 条

四行结论很清楚:1 拿到 ID,2 本人访问 200,3 攻击者换个 UA 重放也是 200, 4 攻击者不带 ID 才 401。 第 3 行是整节的核心:服务端只认那串 ID,不看别的。攻击者从头到尾没碰受害者,只是拿到了那张票。 绑 UA 之后,本人 200、攻击者 401,回的话是「这个会话不是从这台设备发起的」。 但我在正文里说了:这个办法会误伤。换个浏览器、升个版本,用户自己就被锁在外面了。 所以它只能用在敏感操作上。

30 秒实验

  1. 用 document.cookie 在一个没加 HttpOnly 的站点上读一下,看能不能读到。
  2. 想想你常用的网站,登录态一般多久过期?太长了有什么风险?
  3. 想一想:为什么「密码改了要作废所有会话」是一条硬要求?

常见误区

  1. 「HTTPS 加了就偷不到 ID」——XSS 在浏览器内部,加密管不到。
  2. 「绑 IP 很安全」——手机换基站就换 IP,误伤率极高。
  3. 「会话 ID 长一点就安全」——长度不解决「被抄走」这个问题。
自测三问(点开对答案) 1. 为什么说会话 ID 是一张不记名通行证? —— 服务端只认 ID 不认人,谁拿到都能用。 2. HttpOnly 防的是什么? —— 防脚本读 cookie,也就是掐断「XSS 偷会话 ID」这条路。 3. 绑 User-Agent 或 IP 为什么不是好办法? —— 误伤太严重:换浏览器、升级、换网络都会把用户锁在门外。

ID 被偷是一种。还有一种更省事:让受害者用攻击者给的 ID。

相关笔记