会话 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 秒实验
- 用
document.cookie在一个没加 HttpOnly 的站点上读一下,看能不能读到。 - 想想你常用的网站,登录态一般多久过期?太长了有什么风险?
- 想一想:为什么「密码改了要作废所有会话」是一条硬要求?
常见误区
- 「HTTPS 加了就偷不到 ID」——XSS 在浏览器内部,加密管不到。
- 「绑 IP 很安全」——手机换基站就换 IP,误伤率极高。
- 「会话 ID 长一点就安全」——长度不解决「被抄走」这个问题。
自测三问(点开对答案)
1. 为什么说会话 ID 是一张不记名通行证? —— 服务端只认 ID 不认人,谁拿到都能用。 2. HttpOnly 防的是什么? —— 防脚本读 cookie,也就是掐断「XSS 偷会话 ID」这条路。 3. 绑 User-Agent 或 IP 为什么不是好办法? —— 误伤太严重:换浏览器、升级、换网络都会把用户锁在门外。ID 被偷是一种。还有一种更省事:让受害者用攻击者给的 ID。
- ☐ (本节完,下一节 11.4.2 会话固定攻击)