什么是 Web 缓存欺骗
Web 缓存欺骗是攻击者利用“缓存服务器(CDN / 反向代理 / 缓存层)与源站对同一请求的处理不一致”来诱使缓存保存本应是用户私有/动态的内容(例如用户账户页面),然后由攻击者请求同一缓存条目以获取受害者的私密数据。它与“缓存投毒”不同:WCD 是让缓存存储敏感响应,缓存投毒是把恶意内容注入缓存。
为什么会发生
关键在于两个角色如何“解析 URL 与决定是否缓存”:
-
源站(origin):常见框架(REST、MVC)会把 URL 抽象为路由/参数,可能忽略多余路径片段或文件扩展;
-
缓存层(CDN / reverse proxy):通常用字符串或规则匹配 URL(例如:路径以
.css、.js、/static/结尾即视为静态资源并缓存)。
当源站把某 URL 解析为“动态页面”但缓存把它当作“静态资源”并缓存响应时,就产生了可被利用的差异。攻击者只需引诱已登录用户请求一个特制 URL(例如在受害者看到后发起请求),缓存就可能把包含用户数据的响应存到可公开访问的缓存键中。
常见利用方式
-
静态扩展名规则(Static extension rules) CDN 依据路径后缀(
.css、.js、.png等)来缓存。攻击者把目标动态 URL 后追加静态后缀: 例:https://bank.example.com/account/profile/wcd.css -
静态目录规则(Static directory rules) CDN 配置缓存
/static/或/assets/下的内容,攻击者把动态路径放到这些目录下或伪装为该目录。 -
文件名规则(robots.txt / favicon.ico 等特殊名) 有些 CDNs 默认会缓存特定文件名(
robots.txt、favicon.ico)。将动态内容暴露为这些名称可触发缓存。白皮书中就提到这些规则被滥用的例子。 -
路径映射/分隔符/规范化差异
构造攻击的步骤
-
定位敏感动态端点:找出返回用户私密数据的 GET/HEAD/OPTIONS 端点(Session 必须对受害者有效)。
-
测试路径/分隔符/扩展名:对目标 URL 追加任意段或静态后缀,观察 origin 是否仍返回相同内容。示例:
/account→/account/foo,/account/wcd.css。如果 origin 忽略额外段,则可能可利用。 -
触发受害者请求:诱导已登录的受害者点击构造的 URL(社交工程、钓鱼邮件、论坛链接等)。
-
检测缓存:自己发起相同 URL 的请求,观察 response header(如
X-Cache: hit、Cache-Control、响应时间是否显著降低)来判断是否由缓存返回。Burp 的 Param Miner/Cache-buster 插件也常用来避免测试时误命中缓存。
如何检测
-
看响应头:
X-Cache,Via,CF-Cache-Status(Cloudflare),Age等;X-Cache: hit或CF-Cache-Status: HIT表明缓存命中。 -
时间差:首次请求慢,后续同一 URL 显著变快 → 可能是缓存命中。
-
内容-type vs extension 不匹配:如果 URL 以
.css结尾但Content-Type: text/html,要警惕。Cloudflare 的 Cache Deception Armor 就基于这点做防护。 The Cloudflare Blog+1 -
参数化 cache-buster:测试时使用独一无二的 query(
?cb=UNIQUE)来确保你在做“新请求”而不是读已存在缓存。PortSwigger 建议使用 Param Miner/自动 cachebuster。
快速检测/判定步骤
-
访问目标动态页面(带会话 cookie)并记下响应头(是否含
Cache-Control、Set-Cookie、Content-Type)。 -
访问“伪静态”变体(如追加
.css、.js、或放到/static/下):/account/profile.css。看 origin 是否仍返回 200 且内容与原页面类似。 -
读取同一“伪静态” URL 的未登录版本,观察是否能获得相同内容。
-
优先查看这些响应头:
X-Cache,CF-Cache-Status,Age,Cache-Control,Via,Content-Type。Content-Type: text/html而 URL 看起来像静态资源是高风险指示器。 -
用独一无二的 query string 做对照(cache-buster):
/account/profile.css?cb=UNIQUE123保证是否写入新缓存条目再测试。
利用示例
-
目标(受害者登录):
https://example.com/profile返回用户资料(动态 HTML)。 -
测试:
GET /profile/wcd.css→ 源站仍返回相同 HTML(没有 404/302)。 -
诱导受害者访问
https://example.com/profile/wcd.css。 -
缓存把这个 HTML 存为
.css的 key;攻击者随后访问同一 URL(未登录)便能看到缓存中的用户资料。
(真实测试中请使用受控实验环境与授权靶场,如 PortSwigger Labs,切勿在未授权的站点执行攻击。)
风险与影响
-
信息泄露:用户个人资料、会话令牌、邮箱、甚至敏感业务数据被公开缓存。
-
帐户接管 / 横向渗透:泄露的认证凭据或个人信息可被用于进一步利用。
-
品牌/法律风险:敏感数据泄露可能导致合规与隐私问题。 这些风险使得 WCD 在有会话数据的站点(银行、电子商务、SaaS)里尤其严重。
防御
多层次防御:在 origin(应用)层、缓存配置层和 CDN 层分别做控制。
应用/源站层
-
不要在 GET 响应中返回私密数据(或对返回进行更严格的认证检查)。
-
对动态页面设置合适缓存头:
Cache-Control: private, no-store, no-cache, must-revalidate(根据需要选择)。例(nginx):
location /account { add_header Cache-Control "private, no-store, no-cache, must-revalidate";}-
确保对路径参数/多余片段进行显式校验:若 URL 不应含额外扩展/段,则返回 404/302 而非返回用户页面。
-
Content-Type 验证:确保对于请求带某些静态后缀时 Origin 返回的
Content-Type与文件类型一致(若不一致,返回 404 或 400)。这能阻止 CDN 仅凭扩展缓存 HTML。
缓存 / CDN 配置层
-
基于响应的 Content-Type 决定是否缓存(而不仅凭 URL 扩展)。Cloudflare 的“Cache Deception Armor”即是一例,它验证扩展与
Content-Type是否匹配。 -
只缓存明确允许的路径/扩展(白名单策略而非黑名单),并尽量避免“自动”缓存所有看起来像静态的路径。
-
把敏感路径设为不缓存(surrogate-control 或 set header),尤其是账户、会话、支付等相关路由。
-
把缓存键包含更多来源信息(例如 Cookie、Authorization header、Vary header),使缓存更精确并避免把有会话的数据公用。注意性能折中。
监控与检测
- 启用缓存访问日志与 cache analytics,检测异常命中或不符合规则的 content-type/extension 不匹配请求。Cloudflare、Akamai 等 CDN 提供相应功能。
常见误区
-
“只要 origin 返回
Cache-Control: private就万无一失”:并非总然——若 CDN 忽略源站头或被误配置,仍可能缓存;应在多个层面做防护。 -
“WCD = cache poisoning”:两者有交叉但根本不同(WCD 是缓存敏感响应,投毒是注入恶意内容)。分清很重要以选择正确对策。
推荐行动
-
审计:列出所有可能返回敏感数据的 GET/HEAD/OPTIONS 端点。
-
配置:对这些端点在源站返回
Cache-Control: private, no-store。 -
CDN/缓存:启用基于
Content-Type的缓存策略或厂商的“Cache Deception”防护规则(如 Cloudflare 的 Cache Deception Armor)。 -
监控:设置 cache-hit 异常告警、content-type vs extension 的不匹配告警。
-
测试:在授权环境下用 PortSwigger labs、自动化脚本覆盖常见 exploit patterns(扩展名、路径片段、规范化差异等)。
结语
Web 缓存欺骗利用的是“解析与缓存规则的不一致”——防护要点是在源站拒绝不规范路径 / 明确标注缓存策略,并在 CDN 层基于 response(如 Content-Type)而非仅 URL 扩展做缓存决策。做好源站、缓存和监控三层联防,既能保住性能优势,也能避免敏感数据被缓存泄露。