文章总结: 本文深入解析SRC中的缓存欺骗与缓存投毒攻击机制,重点揭示缓存服务器与Web源服务器对动态/静态内容处理差异导致的安全漏洞。攻击者通过构造恶意URL诱导用户访问,使含敏感信息的动态页面被错误缓存为静态资源,造成数据泄露。文章详细对比了正常与存在缓存时的请求流程,并分析了缓存污染与欺骗的区别,但缺乏具体防御方案。 综合评分: 79 文章分类: WEB安全,漏洞分析,实战经验,安全建设,应用安全
SRC中的缓存欺骗与缓存投毒理论与实战
原创
三呼呼 三呼呼
古月安全
2026年5月13日 10:04 四川
在小说阅读器读本章
去阅读
缓存简介
什么是缓存
在Web开发中,缓存是一种存储机制,用于临时保存响应数据,以便后续相同的请求可以更快地被处理,减少对原始服务器的请求压力。这里也只关注web中的缓存内容。 那到底缓存的是些什么内容?
缓存的是HTTP请求的响应内容,包括:包括响应头(如Cache-Control, ETag, Last-Modified等)和响应体(HTML、CSS、JS、图片等实际内容)等。
响应体又包含:
-
静态资源:如图片(PNG、JPEG)、样式表(CSS)、JavaScript文件、字体文件等。这些资源不经常改变,适合缓存。
-
动态资源:有时也可以缓存动态生成的页面或API响应,但需要谨慎,因为内容可能随用户或时间而变化。
哪些内容需要缓存?
不经常变化的静态资源:这些资源可以通过在文件名中添加哈希值(如style.a1b2c3.css)来实现长期缓存,因为每次内容变化都会生成新的文件名。 公开的、非用户特定的内容:例如博客文章、新闻文章等。对于用户特定的内容(如个人资料页面),则不应被缓存,或者只能缓存在私有缓存中(如浏览器缓存,且标记为private)。
通常遵循如下规则:
| | | | | — | — | — | | 内容类型 | 缓存策略 | 理由 | | 静态资源 (CSS、JS、图片、字体) | 长期缓存 Cache-Control: public, max-age=31536000 | 内容不常变化,文件名通常带哈希值 | | 用户私密数据 (个人资料、购物车) | 不缓存或私有缓存 Cache-Control: private, no-store | 每个用户的内容不同,且包含用户敏感信息 | | 公开动态内容 (博客文章、新闻) | 短期缓存 Cache-Control: public, max-age=300 | 内容可能变化,但允许短时间缓存 | | API响应 | 根据内容决定 可设置 Cache-Control: max-age=60 | 数据可能频繁更新 |
缓存的HTTP响应头详解
缓存控制主要通过HTTP响应头来实现。以下是一些关键的响应头:
-
Cache-Control
:这是控制缓存策略的主要头部。主要控制缓存行为的指令,常用值包括: public:响应可以被任何缓存(包括客户端和代理服务器)缓存。 private:响应只能被客户端缓存,不能被代理服务器缓存。通常用于用户特定内容。 no-cache:缓存存储响应,但在使用前必须向服务器验证有效性(即每次使用缓存前都要发送条件请求)。 no-store:禁止缓存任何响应,每次都必须从服务器获取。 max-age=
:指定资源在缓存中存活的最大时间(秒)。例如,max-age=3600表示资源可以缓存1小时。
Expires:一个过时的头部,指定资源的过期时间(HTTP/1.0遗留)。由于服务器和客户端时间可能不同步,现在更推荐使用Cache-Control的max-age。 ETag 和Last-Modified:分别是资源版本标识符,最后修改时间,用于验证缓存资源是否仍然有效。当缓存资源过期时,客户端可以发送一个带有If-None-Match(对应ETag)或If-Modified-Since(对应Last-Modified)的请求,如果资源未修改,服务器返回304 Not Modified,客户端可以使用缓存。
Vary:指定哪些请求头影响缓存
比如下面的相应标识:
# 1. 静态资源 - 长期缓存(推荐方案)Cache-Control: public, max-age=31536000ETag: "abc123"# 注意:通常结合文件名哈希,如 style.33a64df551425fcc55e.css# 2. 用户私密数据 - 禁止缓存Cache-Control: private, no-store, no-cache# 3. 公开动态内容 - 短期缓存Cache-Control: public, max-age=300, must-revalidate# 4. 需要验证的缓存Cache-Control: no-cache # 缓存但每次使用前需验证# 或Cache-Control: max-age=3600, must-revalidate
我们知道百度使用了CDN缓存,看看百度的响应包,这就是有缓存的明显标识:
缓存的位置和类型
客户端缓存(浏览器缓存) 存储在用户的浏览器中 只对单个用户有效 示例:浏览器缓存CSS、JS、图片等文件 代理缓存 位于客户端和服务器之间的网络设备 如:公司防火墙后的缓存、ISP(互联网服务提供商)缓存 为多个用户(如同一个网络中的用户)提供缓存 服务器端缓存 CDN(内容分发网络)节点 反向代理缓存(如Nginx、Varnish) 应用服务器缓存(如Redis、Memcached)
缓存小结
缓存就是一个临时存放常用数据的地方,其主要作用是提前将一些内容存到一个读取速度较快的位置(可以是专门的缓存服务器),下次需要时直读取,从而大幅提升效率。
缓存欺骗
缓存欺骗(Cache Deception Attack)是一种Web缓存投毒攻击,简单来说就是攻击者利用缓存服务器和web源服务器对动态内容和静态内容的处理差异,诱导用户访问特殊构造的URL,使包含敏感信息的动态页面被错误地缓存为静态资源,从而泄露用户隐私数据。其实最重点就是这句话缓存服务器和web源服务器对动态内容和静态内容的处理差异
无缓存web响应流程
首先看看没有缓存的情况下访问的流程 1.用户请求动态页面:用户访问 https://site.com/test.php 2.直接到达源服务器:无中间缓存层,请求直接到应用服务器 3.服务器处理:PHP解释器执行 test.php 脚本 4.动态生成内容:服务器读取会话数据,生成个性化响应 5.返回给用户:响应通常包含 Cache-Control: private 或 no-cache 6.浏览器处理:浏览器可能缓存,但仅限当前用户会话 其关键特点是每个请求都直接到达源服务器,无共享缓存层。 就像这样最简化的流程: 用户访问—》源服务器—》响应内容—》用户
存在缓存web响应流程
再看看有缓存服务器的情况
1.用户请求动态页面:用户访问 https://site.com/test.php 2.请求到达缓存服务器:CDN或反向代理拦截请求 3.缓存服务器检查缓存:查找是否已有缓存副本,通常情况下动态文件php是不会缓存的 4.生成缓存键:通常基于请求方法 + 主机名 + URI 检查缓存存储中是否存在该键 关键动作:默认不包含Cookie等用户标识,因此所有用户的缓存键相同 5.缓存未命中,转发到源服务器:请求被发送到源站 缓存服务器添加代理头:X-Forwarded-For, X-Real-IP 转发原始Cookie和认证信息 标记缓存状态:X-Cache: MISS 6.源服务器处理请求:PHP解释器执行 test.php 脚本 源服务器设置正确缓存头:标记动态内容不可缓存 缓存服务器接收响应并决策:根据缓存头决定是否缓存 7.浏览器处理:浏览器缓存私有响应
相当于是:用户—》缓存服务器—》响应内容—》是否已存在缓存内容—》不存在—》(《—存在立即返回内容)源服务器—》响应内容—》缓存服务器—》用户。省略掉服务器判断是否缓存该内容的情况,这里不重要:使用下面这样的流程图更清晰:
中间经过了缓存服务器的数据响应过程。如果是首次需要缓存的内容:缓存服务器一方面将内容返回给用户,另一方面通常会将此内容保存一份在自己的缓存中,以备后续相同请求使用。
缓存欺骗攻击流程
-
攻击者识别动态页面test.php ,从而构造恶意URL:https://site.com/test.php/static.css
添加静态资源后缀/static.css—缓存服务器当前未记录该条记录
目的是欺骗服务器将此请求识别为静态文件
-
诱导受害者访问恶意URL:通过钓鱼或第三方网站
攻击者诱使受害者访问:https://site.com/test.php/static.css
已登录受害者访问该RUL
请求到达缓存服务器:缓存层接收欺骗请求
-
DNS解析到CDN节点
缓存服务器接收:GET /test.php/static.css
关键误判:路径中包含.css扩展名
-
缓存服务器检查缓存:查找是否已有缓存副本
生成缓存键:GET site.com /test.php/static.css
缓存未命中(首次访问)—缓存服务器当前未记录该条记录
转发请求到源服务器
-
源服务器错误处理请求:配置错误导致错误缓存
服务器看到.css后缀,添加错误缓存头 (Cache-Control: public)
但实际路由执行test.php脚本而非static.css(路径解析歧义),将test.php的执行结果,加入到test.php/static.css 缓存键
-
服务器生成包含敏感数据的响应:但标记为可缓存
Cache-Control: public, max-age=300 # 错误的公共缓存头-所有人均可访问,无需鉴权
Content-Type: text/css # 内容类型错误
-
缓存服务器错误缓存响应:信任错误的缓存头
看到Cache-Control: public,认为可缓存
将响应存入共享缓存,缓存键为GET site.com /test.php/static.css
设置过期时间
-
攻击者请求相同URL获取缓存数据:无需认证
直接访问:https://site.com/test.php/static.css
无Cookie,无认证信息
缓存服务器检查缓存:命中!实际访问的就是刚才受害者返回的test.php的内容
比如这样http://localhost/test/test.php
然后访问http://localhost/test/test.php/staticd.css,其实源服务器都是执行的test.php,但是在缓存服务器上,这2个确是不一样的路径。一个返回的是test.php的执行结果。而staticd.css返回的同样也是test.php的内容,但是这确实2条记录,一条是动态的不会被缓存,一条是静态的会被缓存,而通常静态的是不需要任何权限就可以访问的。如下,虽然是访问的不存在的静态页面,但是服务器确得到的是动态页面的结果,而不是400,未找到页面这种报错。
这就是缓存服务器和web源服务器对动态内容和静态内容的处理差异导致的。
缓存服务器识别内容的逻辑
缓存服务器通常基于简单规则识别静态内容,比如通过后缀名如下:而web源服务器通常拥有更精准的识别方式。
// 缓存服务器通常基于简单规则识别静态内容if (request.path.match(/\.(css|js|jpg|png|gif|ico)$/)) { // 标记为静态内容 isStatic = true;} else if (response.headers['Content-Type']?.includes('text/css')) { // 或根据Content-Type判断 isStatic = true;}
关键处理差异对比
| | | | | — | — | — | | 处理维度 | 缓存服务器 | Web源服务器 | | 静态内容识别 | 基于简单规则(扩展名/Content-Type) | 基于文件存在性和路径规则 | | 动态内容识别 | 依赖响应头(Cache-Control) | 基于脚本扩展名或路由配置 | | 缓存决策权 | 有最终决策权(是否缓存) | 只有建议权(通过响应头) | | 内容理解能力 | 无,不理解内容语义 | 完全理解,执行业务逻辑 | | 默认缓存倾向 | 倾向于缓存(性能导向) | 倾向于不缓存(安全/正确性导向) | | 用户状态感知 | 通常忽略Cookie和会话 | 完全感知和处理用户状态 | | 错误配置影响 | 可能导致大规模数据泄露 | 通常只影响单个请求 | | 性能考量 | 优先考虑缓存命中率和速度 | 优先考虑逻辑正确性和数据一致性 |
关键问题:路径解析歧义
示例请求:GET /test.php/statica.css
缓存服务器的视角
// 缓存服务器看到的:{ path: "/test.php/statica.css", extension: ".css", // 误判为CSS文件 cacheKey: "GET example.com /test.php/statica.css",
// 决策:看起来像静态文件,可能缓存 expectedContentType: "text/css", shouldCache: true // 假设响应头允许}
Web源服务器的视角,正确配置情况:
// Web服务器看到的:{ path: "/test.php/statica.css", scriptFile: "/test.php", // 识别为主脚本 pathInfo: "/statica.css", // 额外路径信息 // 决策:执行test.php,返回动态内容 handler: "php-fpm", isDynamic: true,
// 应设置:Cache-Control: private}
但配置错误时:
# 错误配置:所有.css请求都添加公共缓存头location ~ \.css$ { add_header Cache-Control "public, max-age=300"; # 但没有限制实际处理方式 try_files $uri $uri/ /index.php?$args;}
从而导致了.css在缓存服务器中的表现确是.php的内容。
缓存投毒导致的缓存污染
指攻击者通过某种方式,将恶意构造的HTTP响应存储在缓存服务器(如CDN、反向代理等)中,使得后续用户访问同一URL时,接收到的是被污染的恶意响应,从而达到攻击的目的。 缓存污染与缓存欺骗(Cache Deception)不同。缓存欺骗是诱导用户访问特殊构造的URL,使得包含用户敏感信息的动态页面被缓存,从而泄露信息。而缓存投毒-污染则是攻击者主动向缓存中注入恶意内容,影响后续访问该资源的用户。
比如:无缓存的情况下正常访问https://site.com/page?test=test,访问直接到达源服务器,然后返回响应的处理结果。
存在缓存web响应流程
而有缓存服务器的情况下呢:
1.用户请求页面:用户访问 https://site.com/page?test=test
-
请求到达缓存服务器:CDN或反向代理拦截请求 DNS解析到CDN边缘节点 缓存服务器接收请求:GET /page?test=test3. 3.缓存服务器检查缓存:基于完整URL生成缓存键 生成缓存键:GET site.com /page?test=test 检查缓存存储 缓存未命中(首次访问)
-
转发到源服务器:请求被代理到源站 缓存服务器添加代理头 转发原始请求头和参数 标记:X-Cache: MISS5. 5.源服务器处理请求:安全处理参数并生成响应 接收参数test=test 验证和转义参数值 生成安全HTML响应6. 6.源服务器设置安全缓存头:标记动态内容不可缓存 HTTP/1.1 200 OK Cache-Control: private, no-store Content-Type: text/html; charset=utf-8 …安全的内容… 7.缓存服务器接收响应并决策:根据缓存头决定是否缓存 检查到Cache-Control: private, no-store 决定不缓存此响应 直接返回给用户,标记X-Cache: BYPASS8. 8.浏览器处理:浏览器缓存私有响应 浏览器看到Cache-Control: private 将响应存入私有缓存 后续相同用户请求可能从浏览器缓存读取
缓存污染过程
1.攻击者识别可缓存端点:找到可缓存的动态页面
攻击者测试哪些页面响应包含Cache-Control: public 发现搜索页面可缓存:/search?q=keyword 响应头:Cache-Control: public, max-age=3600
2.攻击者构造恶意参数:注入恶意内容到可缓存响应
比如:正常请求:/search?q=test
提交恶意请求:/search?q=test
3.攻击者发送恶意请求:触发缓存服务器缓存污染内容
攻击者直接访问恶意URL/search?q=test
4.请求到达缓存服务器:缓存层接收恶意请求
缓存服务器接收:GET /search?q=test
生成缓存键:基于完整URL(包括恶意参数)
5.缓存服务器检查缓存:查找是否已有缓存
缓存未命中(首次恶意请求) 转发请求到源服务器
6.源服务器处理请求:可能未正确过滤参数,导致存在XSS漏洞
接收恶意参数值 如果未正确转义,将恶意脚本嵌入test响应 响应错误地标记为可缓存
7.源服务器返回被污染响应:包含恶意内容并标识为可缓存
HTTP/1.1 200 OKCache-Control: public, max-age=3600 # 错误地可缓存Content-Type: text/html; charset=utf-8<html><h1>搜索结果:test<script>alert('xss')</script></h1><!-- 恶意脚本被嵌入页面 --></html>
8.缓存服务器缓存污染响应:信任错误的缓存头
看到Cache-Control: public,认为可缓存 将污染响应存入共享缓存 缓存键包含完整恶意参数 设置3600秒过期时间 返回给攻击者,标记X-Cache: MISS
9.其他用户请求相同URL:触发缓存命中
正常用户搜索”test”:/search?q=test
关键问题:缓存键不同!正常用户不会触发恶意缓存
10.攻击者需要诱导用户访问恶意缓存键:
通过社交工程发送恶意链接 /search?q=test#
11.缓存服务器返回污染内容:用户执行恶意脚本
缓存命中,直接返回污染响应 用户浏览器渲染页面,执行恶意脚本 可能窃取Cookie、会话等敏感信息
还有一种情况这里的/search?q=test与test#“>可能缓存键相同导致的缓存污染,源服务器可能忽略(#之后内容)生成缓存键,也可能导致缓存污染,这样即便是正常访问的用户也会受到影响。
如何识别缓存服务器
要利用缓存相关的攻击首先要确认存在缓存服务器:那么如何进行判断:
1.查看HTTP响应头分析 这是最直接的判断方法,查看响应头中的缓存相关字段:
Cache-Control:max-age=3600→缓存控制标识,缓存1小时,期间直接用,不问服务器 ETag:服务器给资源的唯一标识符(通常是个哈希值) Last-Modified:资源的最后修改日期 X-Cache: HIT 或 X-Cache: MISS → 明确表示有缓存层 CF-Cache-Status: HIT → CloudFlare缓存 Age: <秒数> → 表示内容已在缓存中的时间 Via: 1.1 varnish → 显示使用的缓存服务器 X-Varnish: 123456 → Varnish缓存标识 Server: cloudflare → CloudFlare缓存 X-Proxy-Cache: HIT → 代理缓存
2.DNS记录分析-检查域名解析是否指向CDN提供商
3.Traceroute路由追踪,查看请求经过的中间节点是否是已知CDN节点,最后一跳的IP与DNS查询结果不同等方式。
缓存欺骗实战
现在我们使用burpsuit提供的测试服务器认识一下它的漏洞危害,并逐渐掌握该漏洞的利用和测试:
https://portswigger.net/web-security/learning-paths/web-cache-deception/wcd-using-path-mapping-discrepancies/web-cache-deception/lab-wcd-exploiting-path-mapping# 首先需要注册一下,然后登录就行了,就可以进入如下的界面:
启动环境之后是这样的
使用burpsuit抓包可以看到响应包中存在明显的cache标识符。
然后其提供的账号密码登录
在自己的my-account页面可以看到api key:
漏洞利用
我们在前面的抓包看到.svg明显是有缓存的。所以这里我们尝试通过.svg后缀试试看:
果然在RUL后面加入了/123.svg的路径,还是返回的my-account的页面的内容
那么现在我们就已经可以构造一个缓存欺骗的页面了:https://0a83009b03585eb28097f87b001e000c.web-security-academy.net/my-account/123.svg 比如这样的格式,发送给已经登录过的用户访问,假设给当前用户,它访问该URL之后,会返回自己的my-account页面信息。
然后现在我们用其他浏览器打开这个url地址:https://0a83009b03585eb28097f87b001e000c.web-security-academy.net/my-account/123.svg,发现返回了wiener的主页信息。
这样就完成了一次缓存欺骗的攻击流程。
在burpsuit提供的测试服务器中,还存在其他不同类型的缓存欺骗,主要原理一样,只是被利用的服务器处理静态资源的逻辑不同。
比如后面的这个利用分隔符分号;的方式。
一样的过程直接带过,让已经登录过的账号访问
https://0a8d00a304cfac458376232400d500b3.web-security-academy.net/my-account;123.js,然后其他浏览器打开直接就可以看到其敏感信息。
当然这种服务器与缓存服务器的处理差异不止些,比如如果源服务器解析经过编码的url路径,而缓存服务器不解析,可以尝试利用这种差异,按照以下结构构造攻击载荷:同样得到了敏感信息
通常可利用的差异方式有:利用静态扩展名缓存规则,使用路径映射差异,使用分隔符差异,使用分隔符解码差异,利用静态目录缓存规则,使用规范化差异,利用文件名缓存规则。
如何预防Web缓存欺骗漏洞
可以采取一系列措施来预防Web缓存欺骗漏洞: 始终使用Cache-Control头标记动态资源,设置指令为no-store和private。 配置CDN设置,确保缓存规则不会覆盖Cache-Control头。 激活CDN提供的针对Web缓存欺骗攻击的防护功能。许多CDN允许您设置缓存规则,以验证响应Content-Type是否与请求URL的文件扩展名匹配。例如,Cloudflare的Cache Deception Armor。 核心就是确保源服务器和缓存服务器在解释URL路径时不存在任何差异。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:古月安全 三呼呼 三呼呼《SRC中的缓存欺骗与缓存投毒理论与实战》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。







评论