导语:上一讲 day15 把 WebSocket 通道里的明文通信、XSS、服务端注入捋了一遍,今天切到 Web 缓存层。Web Cache Deception 和 Web Cache Poisoning 方向相反——前者把受害者的私有页面骗进 CDN 缓存让第二个用户未登录就能读,后者往共享缓存里塞恶意响应让所有用户看到错误内容。前者更隐蔽,依赖的只是 URL 重写规则 + 缓存键缺失细分。
一、Web Cache Deception 是什么
Web 缓存欺骗(Web Cache Deception,下文简称 WCD)的核心思路:让 CDN/反向代理误以为某个 URL 请求的是静态资源,把它缓存到边缘节点;但应用层返回的是用户的私有动态页面。受害者访问后,缓存里就有了他的私密信息;攻击者用同一 URL(不携带任何 Cookie)直接拉缓存,把受害者信息拿走。
和 Web Cache Poisoning(缓存投毒)的关键区别:
- 欺骗:缓存里的是合法的受害者私有响应,但攻击者能拿到它。攻击目标是”读取别人的私密数据”。
- 投毒:缓存里的是攻击者构造的恶意响应,所有后续访问这个 URL 的用户都被污染。攻击目标是”让所有人看到攻击者控制的内容”。
WCD 的经典案例是 2017 年 Omer Gil 在 GitHub 上演示的——他在 GitHub Pages 个人主页 URL 末尾加 .css,成功让 GitHub 把他的个人信息页面缓存到 CDN 上;另一个未登录的浏览器直接拉到了他的页面内容。GitHub 当年紧急修复了相关路由。
二、Path Confusion:URL 重写是攻击前提
WCD 之所以能成立,前提是后端框架或 Web 服务器的 URL 重写规则把”动态路径 + 静态后缀”当作同一个路由处理。最常见的情况:Django、Express、Spring MVC、Nginx 的 rewrite(重写规则)用正则做前缀匹配。
原文给的 Django 示例非常典型:
from django.conf.urls import url
patterns = [url(r'^myprofile/', ...)]
这段正则只规定”以 myprofile/ 开头”的请求都路由到 myprofile 视图,没规定结尾必须是什么。于是这两个 URL 走的是同一个视图:
http://target.example/myprofile— 正常用户访问http://target.example/myprofile/test.css— 加了静态后缀,依然路由到 myprofile 视图
服务端的视图层完全不关心 .css 是什么,它照样从 session(会话)里取当前用户、查数据库、渲染个人页面。问题出在缓存层——CDN 看到 URL 结尾是 .css,按文件扩展名缓存策略,把响应当成静态资源缓存了。
各种框架的类似表现:
| 框架/服务器 | 易触发场景 | 备注 |
|---|---|---|
Django urlpatterns | 正则前缀匹配 + 视图不校验路径 | 最经典 |
| Express.js | app.use('/api', router) + 静态目录托管 | 常见 |
Nginx location / | try_files $uri $uri/ /index.php | 静态文件 fall-through |
| Spring MVC | @RequestMapping("/user/**") | 通配符匹配 |
| IIS | URL Rewrite 默认规则 | Windows 平台常见 |
三、缓存机制回顾:为什么 CDN 会”上当”
WCD 离不开 CDN 的缓存行为。回顾三个关键点:
Cache-Control 指令——响应头里告诉缓存如何处理这个响应:
public— 任何缓存都可以存private— 只能浏览器缓存,不能被 CDN/代理缓存no-store— 任何缓存都不能存max-age=N— 缓存 N 秒
Vary 头——告诉缓存”用什么字段区分缓存版本”:
Vary: Cookie— 不同 Cookie 视为不同缓存版本(最严)Vary: Accept-Encoding— 不同编码视为不同版本- 没有 Vary 头 — 缓存把所有请求当成同一个缓存版本
Cache Key(缓存键)——CDN 用什么区分不同请求的缓存:
- 默认只包含 URL 路径 + 查询字符串,不包含 Cookie
- 所以同一 URL,登录用户和未登录用户的响应会被 CDN 当成同一个缓存项
WCD 的攻击链之所以能打通,就是因为后端应用没有设置 Cache-Control: private, no-store,同时响应头里也没 Vary Cookie,CDN 缓存键不带 Cookie,URL 又能通过路径混淆骗到同一个视图——三个条件凑齐就成了。
四、五步攻击链实战
原文给出的五步流程,配上实战细节:
Step 1:找含敏感信息的页面 登录目标应用,找出含个人信息的页面——/myprofile、/account、/dashboard、/orders、/settings 这些最常见。观察响应头里有没有 Cache-Control: private, no-store——如果没有,就是潜在攻击面。
Step 2:构造欺骗 URL 在路径末尾追加静态文件后缀:
http://target.example/myprofile/nonexistent.css
http://target.example/account/x.jpg
http://target.example/dashboard/payload.js
挑目标应用里不存在的文件名,避免触发其他路由(很多框架会优先匹配静态文件目录)。
Step 3:诱导受害者访问 这一步是必须的——CDN 缓存要有人访问过这个 URL 才会存响应。常见诱导手法:
- 钓鱼邮件里贴这个 URL,”看看你的照片”/”点我看你的主页”
- 评论区/XSS 注入里嵌
<img src="http://target.example/myprofile/x.css">——浏览器会自动请求 - 任何能控制受害者浏览器发请求的地方都行
Step 4:受害者点击访问 受害者已登录态下访问 http://target.example/myprofile/x.css,后端路由命中 myprofile 视图,渲染出受害者个人信息;CDN 看到 .css 后缀 + 响应头允许缓存,把它存到边缘节点。
Step 5:攻击者未登录拉取 攻击者开一个完全无登录态的浏览器(或用 curl 不带 Cookie),访问同一 URL:
curl http://target.example/myprofile/x.css
# 返回受害者的个人信息 HTML
五、检测与识别
自动化检测可以借 Burp Suite 的扩展和现成 PoC:
- Burp Cache Deception Scanner — 自动遍历带 Cookie 的页面,挨个加
.css、.jpg测试缓存行为 - Web Cache Deception Burp 插件 —
Smuggler风格 - 手工检测流程:登录后访问
/myprofile/x.css→ 看响应是否有Cache-Control: no-store、Vary: Cookie、响应码是否是 200 → 看 CDN 是否返回Age头(说明被缓存了)
判定标准:
- 受害者访问后,CDN 响应里出现
Age、X-Cache: HIT、CF-Cache-Status: HIT等字段 → 已缓存 - 未登录访问同一 URL 能拿到登录态下的内容 → WCD 成立
六、防御方案
按层级给:
应用层:
- 敏感路径响应头强制
Cache-Control: private, no-store - 关键响应加
Vary: Cookie或Vary: Authorization - 视图层校验完整路径,不接受
myprofile/x.css这种后缀混搭
框架层:
- Django 用更精确的正则:
url(r'^myprofile/?$', ...),锚定结尾 - Express.js 不混用
app.use静态托管和动态路由 - Nginx
location块用精确匹配(= /myprofile)或正则(~ ^/myprofile$)
CDN 层:
- 默认开启”区分 Cookie 的缓存键”(Cloudflare 高级规则可配)
- 敏感路径走”绕过缓存”规则
- 监控
X-Cache: HIT在敏感路径上的出现频次
HTTP 层补充:
- 加
X-Content-Type-Options: nosniff,避免响应被当成 CSS/JS 解析 - CSP(内容安全策略)限制内联脚本,即便攻击者拿到 HTML 也难以触发脚本执行
七、思考题
- 某应用
/api/me强制Cache-Control: no-store,但/api/me.json(带后缀)没有——攻击者能不能改 Path 让 JSON 后缀也路由到/api/me视图? - CDN 开启了”按 Cookie 区分缓存键”,但应用只用 Cookie 做语言偏好(
lang=en/lang=zh)——攻击者能不能伪造 Cookie 让 CDN 命中受害者那条缓存? - WCD 和 CSRF(跨站请求伪造)都能诱导受害者访问特定 URL,两者诱导的方式有什么本质区别?
下一篇 day17 会拆解 Session Puzzling——多应用复用同一 session 时的逻辑混淆攻击。
八、素材出处
- Learn365 Day 16 原文:
https://github.com/harsh-bothra/learn365/blob/main/days/day16.md - Web Cache Deception Attack(Omer Gil 原文):
https://omergil.blogspot.com/2017/02/web-cache-deception-attack.html - PortSwigger Web Cache Deception 实验:
https://portswigger.net/web-security/web-cache-deception - GitHub 2017 WCD 案例:
https://github.blog/security/web-cache-deception-attack/














暂无评论内容