全面解析Web缓存攻击:突破规则限制的实战指南
背景
Web缓存
Web缓存技术自互联网诞生之初便已存在。该技术通过使用密钥对请求进行指纹识别(大多数情况下会使用请求URL的部分或全部内容构建密钥),并将密钥与存储的静态响应进行映射。
近年来,大多数生产系统通过配置CloudFlare、Akamai或CloudFront等提供商的CDN来集成缓存。CDN可视为分布在全球的Web缓存代理网络,通过提供静态响应来提高系统效率和可扩展性。
本文重点关注不同应用服务器和CDN代理之间存在的URL解析差异,但相同技术可应用于任何类型的Web缓存,包括源服务器本身集成的缓存。
投毒与欺骗
由于Web缓存在现代系统中扮演关键角色,研究人员一直在寻找利用它们存储动态信息的方法。这些信息可用于获取敏感数据或传递恶意负载。此类攻击通常针对两个过程之一:密钥生成或缓存规则分析。
计算密钥至关重要,因为具有相同指纹的每个请求都应生成相同的响应,无论何时发送或包含什么额外信息(如正文或额外标头)。如果响应基于特定标头的值而变化,则该值应成为密钥的一部分。使用旨在匹配错误密钥的恶意负载存储消息称为Web缓存投毒。
缓存规则旨在识别响应是否为静态且应存储。未能缓存静态资源会影响性能,但存储包含经过身份验证用户的敏感信息的动态响应可能会对应用程序造成毁灭性影响。如果攻击者能够制作检索并缓存用户数据的恶意请求,他们可能能够劫持令牌和API密钥,可能导致完全账户接管。这被称为Web缓存欺骗。
URL差异
为了评估缓存规则、计算缓存密钥并映射端点处理程序,源服务器必须提取所请求资源的绝对路径。这是通过使用路径分隔符和规范化来解析URL完成的。
如果缓存和应用服务器的解析器不同,则可能利用差异来改变URL的含义。这可能使您能够控制存储哪些响应以及用于访问它们的密钥。
分隔符
URL RFC将某些字符定义为分隔符,例如分号或问号。但规范相当宽松,允许每个实现向此列表添加自定义字符。
这项研究表明,许多流行框架和HTTP服务器使用不同的字符作为分隔符。这可能在源服务器和缓存解析器之间创建路径混淆。
源分隔符 各种应用服务器和框架中使用的以下自定义分隔符:
-
Spring中的分号:在许多Java框架(包括Spring)中,分号用作分隔符以包含矩阵变量。矩阵变量可包含在每个路径段中,并不解释为绝对路径的一部分。
- URL: /MyAccount;var1=val → 路径: /MyAccount
- URL: /hello;var=a/world;var1=b;var2=c → 路径: /hello/world
-
Rails中的点:Ruby on Rails允许客户端发送带有格式化程序扩展的路径,该扩展定义响应中返回的视图。这用于返回具有不同内容类型的不同响应。如果未使用扩展或服务器无法识别扩展,则返回默认HTML视图。因此点字符可用作路径分隔符。
- URL: /MyAccount.html → 路径: /MyAccount(默认HTML视图)
- URL: /MyAccount.css → 路径: /MyAccount(CSS视图或不存在时错误)
- URL: /MyAccount.aaaa → 路径: /MyAccount(默认HTML视图)
-
OpenLiteSpeed中的空编码字节:此HTTP服务器使用空编码字节作为经典分隔符来截断路径。
- URL: /MyAccount%00aaa → 路径: /MyAccount
-
Nginx中的换行编码字节:当Nginx配置为重写请求路径时,编码的换行字节用作路径分隔符。重写规则必须映射前缀或URL,而不是整个路径名(这在Nginx中很常见)。
- 规则: rewrite /user/(.*) /account/$1 break;
- URL: /users/MyAccount%0aaaa → 路径: /account/MyAccount
检测源分隔符
- 识别不可缓存请求。查找不具有幂等性的方法(如POST)的请求,或具有Cache-Control: no-store或Cache-Control: private标头的响应。响应(R0)将用于比较URL中有趣字符的行为。
- 发送相同请求,但此次在路径末尾附加随机后缀,例如,如果原始路径为/home,则发送请求至/homeabcd。如果响应(R1)与R0相同,则使用不同端点重复步骤1和2。
- 发送与步骤2相同的请求,但在随机后缀前包含潜在分隔符。如果测试的分隔符是$,则路径应类似于/home$abcd。将此响应(R2)与基准响应(R0)比较。如果消息相同,则该字符或字符串用作分隔符。
要一次测试多个可能的分隔符,可使用Burp Intruder和包含所有ASCII字符的单词列表。确保测试字符的未编码和URL编码版本。
检测缓存分隔符 缓存服务器通常除问号外不使用分隔符。可使用静态请求和响应测试此情况:
- 通过查找从缓存检索响应的证据来识别可缓存请求。例如,通过响应时间分析,或查找值为hit的X-Cache标头。此响应(R0)将用于比较URL中有趣字符的行为。
- 发送相同请求,URL路径后缀后跟可能的分隔符和随机值。GET /static-endpoint<分隔符><随机>
- 将响应与R0比较。如果消息相同,则该字符或字符串用作分隔符。
规范化
缓存和源服务器都使用URL解析器来提取用于端点映射、缓存密钥和规则的路径。首先,识别路径分隔符以定位路径名的开始和结束。提取路径后,通过解码字符和删除点段将其规范化为其绝对形式。
编码 有时,需要发送分隔符字符供应用程序而非HTTP解析器解释。对于这种情况,URI RFC定义了URL编码,允许对字符进行编码以避免修改路径名的含义。
许多HTTP服务器和代理(包括Nginx、Node、CloudFlare、CloudFront和Google Cloud)在解释路径名之前解码某些分隔符字符。更糟糕的是,此过程不一致。这意味着即使没有任何自定义配置,相同的URL在最流行的CDN和源服务器中也会具有不同的含义。
此外,RFC未指定应如何转发或重写请求。许多代理解码URL并使用解码值转发消息。如果发生这种情况,下一个解析器可能使用解码字符作为分隔符。因此,如果代理收到以下请求,%3F字符将转换为问号符号: “/myAccount%3Fparam” → “/myAccount?param”
不同的缓存代理还支持许多其他编码。尽管大多数默认不使用,但可以配置CloudFlare或CloudFront等CDN以应用自定义转换并为缓存或访问控制目的解码路径。
检测解码行为 要测试字符是否被解码,比较基准请求与其编码版本。例如: /home/index → /%68%6f%6d%65%2f%69%6e%64%65%78 注意:分别编码每个字符可能有用,因为有时特定字符(如斜杠或其他保留字符)不会被解码。
如果响应与基准响应相同且未从缓存获取(无缓存命中标头),则源服务器在使用路径之前对其进行解码。
如果响应可缓存,可以检测缓存解析器的解码行为。发送原始请求后跟编码版本。如果两个响应包含相同的缓存标头,则意味着第二个是从代理获取的,并且密钥在比较之前已被解码。
点段规范化 URI RFC还定义了如何处理URL中的点段,并提供了简单算法来规范化路径。虽然此功能对于从相对路径引用任何资源至关重要,但它也是许多漏洞的来源。
通过利用解析器之间的差异来修改缓存规则的行为并获取精心制作的密钥,可以开发利用点段规范化。甚至像Apache和Nginx这样的流行HTTP服务器解析URL的方式也完全不同,这意味着如果不存在路径混淆漏洞,就无法使用相同的缓存代理。
检测点段规范化 以下技术可用于检测缓存和源服务器处的点段规范化。这些测试可以使用编码的路径遍历负载进行扩展,以了解是否应用了特殊解码。为此,使用相同的请求/响应并将点段替换为编码版本。
要检测源服务器中的规范化,向已知路径发出不可缓存请求(或带有缓存破坏器的请求),然后发送带有路径遍历序列的相同消息: GET /home/index?cacheBuster GET /aaa/../home/index?cacheBuster 或 GET /aaa..\home/index?cacheBuster 如果响应相同,则意味着路径在映射到资源之前已规范化。这可能发生在源服务器处或在代理转发之前。无论哪种方式,点段都会被解析并可用于引用现有资源。
要检测Web缓存处的规范化,重复相同过程但使用可缓存响应,并比较X-Cache和Cache-Control标头以验证资源是否从缓存内存中获取。
规范化差异 下表说明了不同HTTP服务器和Web缓存代理如何规范化路径 /hello/..%2fworld。有些将路径解析为 /world,而其他则根本不规范化。
任意Web缓存欺骗
当Web缓存从源服务器接收响应时,它必须决定资源是否为静态并因此应存储。这涉及对请求和响应应用预定义的可自定义规则。
本节重点介绍使用URL确定是否应缓存响应的规则。这些规则在生产环境中很流行,大多数CDN默认包含其中一些规则。
可以利用解析差异来利用缓存规则,存储动态响应并劫持为受害者生成的敏感信息。有关如何使用URL映射中的差异创建路径混淆的详细说明,请参阅Omer Gil的白皮书《Web缓存欺骗攻击》。
本白皮书重点关注其他类型的差异,这些差异可被利用来劫持任何任意响应,而不仅仅是源服务器具有特殊端点映射的响应。
限制
由于攻击者需要生成受害者浏览器使用的链接,负载必须仅包含安全的URL字符 - 浏览器在发送前不会编码的字符。
要可视化此场景,将浏览器视为通过编码某些字符和删除段来重写请求URL的代理。
静态扩展 大多数CDN提供商(如CloudFlare和Akamai)存储具有静态扩展的资源的响应。这意味着如果请求的路径以 .js 或 .css 等字符串结尾,缓存代理会将响应视为静态。它存储响应并用于服务请求相同路径的其他客户端。
每个CDN或缓存代理定义自己识别的静态扩展列表。下图显示了CloudFlare列出的扩展:
利用静态扩展 当字符被源服务器用作分隔符但代理未使用时,可以包含任意后缀以触发缓存规则并存储任何敏感响应。
例如,如果美元符号字符是源服务器中的分隔符但不是代理中的分隔符,则以下链接存储对 /myAccount 的响应,允许攻击者劫持敏感信息:您还可以使用编码字符或字符串的相同技术。当源服务器在解析URL之前解码分隔符,或者缓存在转发请求之前重写路径时,这很有用。
例如,未编码的井号符号不适用于缓存欺骗,因为浏览器不发送它,但如果编码,它可以用于利用:您可以使用此攻击的变体来利用转发转换中的差异。如果多个解析器重写请求,我们可以通过应用多个编码和/或分隔符来攻击链中的特定缓存代理:
静态目录 所有CDN中实现的流行规则允许用户创建匹配自定义URL路径前缀的规则。这可用于让Web缓存知道特定目录中的每个资源都是不可变的,应存储,无论资源名称或扩展名如何。静态目录的一些常见示例是:
/static /assets /wp-content /media /templates /public /shared
利用带分隔符的静态目录 如果字符被源服务器用作分隔符但未被缓存使用,并且缓存在应用静态目录规则之前规范化路径,您可以在分隔符后隐藏路径遍历段,缓存将解析该段: GET /<动态资源><分隔符><编码点段><静态目录> 编码点段很重要。否则受害者的浏览器将解析它并且不会转发原始恶意路径。Amazon CloudFront、Microsoft Azure和Imperva默认在评估缓存规则之前规范化路径。
利用带规范化的静态目录 当源服务器在映射端点之前规范化路径并且缓存在评估缓存规则之前不规范化路径时,您可以添加仅由源服务器处理的路径遍历段: GET /<静态目录><编码点段><动态资源>Cloudflare、Google Cloud和Fastly在评估缓存规则之前不规范化路径。如果源服务器在将请求与端点处理程序映射之前规范化路径,例如Nginx、Microsoft IIS和OpenLiteSpeed,则可以利用任何静态目录规则。
当将Microsoft IIS与任何不转换反斜杠的Web缓存结合时,会出现另一个规范化差异。这些缓存将编码的反斜杠解释为常规斜杠。由于没有测试的CDN识别此转换,因此IIS在与此类产品一起使用时容易受到攻击。
静态文件 某些文件,如 /robots.txt、/favicon.ico 和 /index.html,可能不在静态目录中或具有静态扩展,但预计在每个网站中都是不可变的。要存储这些文件,可以创建在路径中查找文件名完全匹配的缓存规则。像CloudFlare这样的CDN默认具有此规则,并始终存储 robots.txt 或 favicon.ico 的响应。
利用静态文件 要利用静态文件规则,可以使用与静态目录相同的技术,当存在前端规范化和后端分隔符时。在这种情况下,静态目录被替换为文件名和缓存破坏器以避免命中缓存资源: GET /<动态资源><分隔符><编码点段><静态文件>
任意Web缓存投毒
当响应被视为静态时,它使用从原始请求派生的密钥存储在缓存中。任何具有相同密钥的未来请求都将使用存储的资源提供服务。
密钥通常使用URL和主机标头生成。可以自定义它们以使用其他标头或请求元素。
在经典Web缓存投毒中,攻击者尝试使用用户在浏览易受攻击网站时请求的URL密钥存储恶意响应。路径访问越频繁,受恶意负载影响的受害者就越多。您可以在James Kettle的研究《实用Web缓存投毒》和《Web缓存纠缠:投毒的新途径》中阅读有关查找Web缓存投毒漏洞的更多信息。
攻击是有限的,因为在许多情况下,投毒路径不受攻击者控制,并且需要用户交互。例如,考虑一个从未被访问的URL,要么是因为它需要特定参数,例如 /home?param=XSS,要么是因为路径本身包含负载 /
但是,将路径混淆与Web缓存投毒漏洞结合可以修改缓存密钥并毒化高请求资源,如网站主页。在这种情况下,对可使用的字符没有限制,因为攻击不需要用户交互,这意味着负载可以通过像Burp Suite这样的HTTP编辑器/中继器发送。
密钥规范化
规范化URL通常被认为是获取所请求资源的绝对路径的安全操作。但是,如果源服务器不以相同方式解释路径,则在缓存密钥中解析点段和编码可能允许攻击者毒化任意资源。
以下所有攻击都假设在生成缓存密钥之前对URL进行了规范化。这可以在大多数CDN中配置,并且是Microsoft Azure和Imperva中的默认行为。
利用映射差异 当源服务器使用特殊映射或在生成响应之前不规范路径时,可以控制用于存储资源的密钥。
此情况的经典示例是访问不存在的端点时具有自反射XSS的应用程序。
考虑以下请求/响应: GET / HTTP/1.1 Host: server.com
HTTP/1.1 404 Not Found Content-Type: text/html Cache-Control: public
Not Found / 恶意负载是URL的一部分,并反映在可缓存响应中。但是,如果没有与攻击者的交互,有效用户永远不会向 / 发出请求。因此,即使响应也可以通过编码版本 /%3Cscript%3EX%3C/script%3E 访问(密钥被解码),攻击者需要向受害者发送链接,就像在反射XSS场景中一样。
但是,如果密钥被规范化,以下负载将使用恶意响应毒化高访问端点,如 /home: GET /<后端路径><路径遍历><投毒路径>此示例中使用双点段,因为负载已包含斜杠。调整路径遍历以解析为所需的投毒端点。如果后端路径占位符使用特殊映射,可以应用相同技术。
利用后端分隔符 当字符被源服务器用作分隔符但未被缓存使用时,可以为可缓存资源生成任意密钥。分隔符将阻止后端解析点段。 GET /<后端路径><分隔符><路径遍历><投毒路径>
利用前端分隔符 在Web缓存欺骗攻击中,解析差异是由分隔符仅在源服务器中使用但不在缓存中使用引起的。找到对缓存服务器有特殊含义且可以通过浏览器发送的字符是罕见的。但是,由于Web缓存投毒不需要用户交互,像井号这样的分隔符可以创建路径混淆。这很有用,因为许多HTTP服务器、CDN和后端框架对片段的解释不同,如下表所示:因此,在像Microsoft Azure这样规范化路径并将井号视为分隔符的情况下,可以使用它来修改存储资源的缓存密钥: GET /<投毒路径><前端分隔符><路径遍历><后端路径>此技术可应用于缓存使用的任何分隔符。唯一要求是密钥被规范化,并且路径与分隔符后的后缀一起转发。
Cache-What-Where
在针对渗透测试或错误赏金计划审核网站时,常见由于浏览器约束和限制而无法利用的漏洞。这些问题需要用户交互,并且无法通过浏览器发送,因为请求需要特定的精心制作的标头或URL中会被编码的字符。
通过将这些漏洞与先前描述的缓存投毒和欺骗技术结合,攻击者可以利用它们并在缓存中存储恶意负载。
例如,考虑具有开放重定向的网站,其中位置是使用 X-Forwarded-Host 标头生成的: GET /home HTTP/1.1 Host: server.com X-Forwarded-Host: evil.com
HTTP/1.1 302 Found Location: http://evil.com/index.html
本身,此重定向未存储在缓存中,因此不应可能用它毒化缓存。但是,如果缓存密钥和后端解析器之间存在差异,此"不可利用"漏洞可能升级为完全域接管。例如,如果Web应用程序在主页上加载 /main.js 脚本,我们可以毒化缓存中的路径并将浏览器重定向以加载恶意脚本:这强制缓存代理将重定向响应存储到 evil.com 在 /main.js 密钥下。当受害者加载主页并尝试访问 /main.js 资源时,恶意重定向将获取由攻击者控制的JavaScript,该JavaScript将感染每个用户浏览器。
在更糟糕的情况下,由于缓存标头,开放重定向被存储: GET /redirect?somePage HTTP/1.1 Host: vulnerable.com X-Forwarded-Host: evil.com
HTTP/1.1 302 Found Location: http://evil.com/somePage Cache-Control: public, max-age=3600
在这种情况下,投毒路径不需要静态扩展,并且可以利用漏洞完成任意缓存投毒和完整网站污损。相同技术可用于任何其他需要用户交互或自反射问题,如自反射且不可利用的XSS。
防御
防御Web缓存欺骗的最简单方法是用Cache-Control标头标记所有动态生成的响应,设置为 no-store 和 private 指令。这告诉Web缓存资源永远不应存储。
同样重要的是验证缓存规则不具有优先于Cache-Control标头的权限。这可以在大多数CDN中配置。如果无法配置,考虑禁用缓存规则或避免使用与CDN解析URL不同的源服务器或框架。
要防止缓存密钥混淆,请确保缓存密钥未规范化,并且缓存分隔符后的后缀未转发到应用服务器。如果不可能,考虑切换到以尽可能相似的方式解析URL的不同CDN或HTTP服务器。
要点
- URL解析差异可以轻松利用Web缓存投毒和欺骗进行利用
- exploitation技术可应用于无数系统和错误赏金计划
- 链式Web缓存投毒和欺骗以增加严重性并获得完全站点接管!