全面解析Web缓存利用:突破规则实现任意缓存投毒与欺骗

本文深入探讨了Web缓存利用技术,通过分析HTTP服务器与代理的URL解析差异,揭示了路径混淆漏洞。文章详细介绍了利用解析器差异实现任意缓存投毒与欺骗的方法,包括静态扩展、目录和文件规则的利用,以及结合开放重定向等漏洞实现全站接管的高级攻击技术。

Gotta cache ’em all: bending the rules of web cache exploitation

Martin Doyhenard
Researcher
@tincho_508

发布时间: 2024年8月8日 22:27 UTC
更新时间: 2024年10月17日 13:41 UTC

多年来,我们已经看到许多利用Web缓存劫持敏感信息或存储恶意负载的攻击。然而,随着CDN的普及,专有URL解析器之间的新差异表明我们只看到了冰山一角。

本文探讨了不同HTTP服务器和代理在解析特制URL时的行为,并研究了导致路径混淆的RFC模糊性。文章还介绍了一套新技术,可用于利用解析器差异,在无数网站和CDN提供商中实现任意Web缓存投毒和欺骗。本研究还提供可打印/下载的PDF格式版本。您也可以在此观看我在DEFCON上的演讲录像:

大纲

  • 背景
    • Web缓存
    • 投毒与欺骗
    • URL差异
    • 分隔符
    • 规范化
  • 任意Web缓存欺骗
    • 限制
      • 静态扩展
      • 静态目录
      • 静态文件
  • 任意Web缓存投毒
    • 密钥规范化
    • 利用后端分隔符
    • 利用前端分隔符
  • Cache-What-Where
  • 防御

背景

Web缓存

Web缓存自互联网诞生以来就一直存在。该技术通过使用密钥对请求进行指纹识别(在大多数情况下,密钥将使用请求URL的部分或全部部分构建)并将密钥与存储的静态响应映射来工作。

近年来,大多数生产系统通过设置内容分发网络(CDN)(如CloudFlare、Akamai或CloudFront)来整合缓存。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

检测源分隔符

  1. 识别不可缓存请求。寻找具有非幂等方法(如POST)的请求,或具有Cache-Control: no-store或Cache-Control: private标头的响应。响应(R0)将用于比较URL中有趣字符的行为。
  2. 发送相同的请求,但这次在路径末尾附加一个随机后缀,例如,如果原始路径是/home,则发送请求到/homeabcd。如果响应(R1)与R0相同,则使用不同端点重复步骤1和2。
  3. 发送与步骤2相同的请求,但在随机后缀前包含一个潜在分隔符。如果测试的分隔符是$,则路径应类似于/home$abcd。将此响应(R2)与基础响应(R0)进行比较。如果消息相同,则该字符或字符串用作分隔符。

要同时测试多个可能的分隔符,可以使用Burp Intruder和包含所有ASCII字符的单词列表。确保测试字符的未编码和URL编码版本。

检测缓存分隔符

缓存服务器通常除了问号外不使用分隔符。可以使用静态请求和响应测试这一点:

  1. 通过寻找响应从缓存中检索的证据来识别可缓存请求。例如,通过响应时间分析,或查找值为hit的X-Cache标头。此响应(R0)将用于比较URL中有趣字符的行为。
  2. 发送相同的请求,URL路径后缀后跟可能的分隔符和随机值。GET /static-endpoint
  3. 将响应与R0比较。如果消息相同,则该字符或字符串用作分隔符。

规范化

URL解析器由缓存和源服务器使用,以提取用于端点映射、缓存密钥和规则的路径。首先,识别路径分隔符以定位路径名的开始和结束。提取路径后,通过解码字符和删除点段将其规范化为绝对形式。

编码

有时,需要发送分隔符字符供应用程序而不是HTTP解析器解释。对于这种情况,URI RFC定义了URL编码,允许对字符进行编码以避免修改路径名的含义。

许多HTTP服务器和代理(包括Nginx、Node、CloudFlare、CloudFront和Google Cloud)在解释路径名之前解码某些分隔符字符。更糟糕的是,这个过程不一致。这意味着即使没有任何自定义配置,相同的URL在最流行的CDN和源服务器中也会有不同的含义。

此外,RFC未指定应如何转发或重写请求。许多代理解码URL并使用解码值转发消息。如果发生这种情况,下一个解析器可能使用解码字符作为分隔符。因此,如果代理收到以下请求,%3F字符将转换为问号符号: “/myAccount%3Fparam” → “/myAccount?param”

还有许多其他编码由不同的缓存代理支持。尽管大多数默认不使用,但可以配置CDN(如CloudFlare或CloudFront)以应用自定义转换并为缓存或访问控制目的解码路径。

检测解码行为

要测试字符是否被解码,比较基础请求与其编码版本。例如: /home/index → /%68%6f%6d%65%2f%69%6e%64%65%78

注意:单独编码每个字符可能有用,因为有时特定字符(如斜杠或其他保留字符)不被解码。

如果响应与基础响应相同且未从缓存获得(无缓存命中标头),则源服务器在使用路径之前解码路径。

如果响应可缓存,则可以检测缓存解析器的解码行为。发送原始请求后跟编码版本。如果两个响应包含相同的缓存标头,则意味着第二个是从代理获得的,并且密钥在比较之前被解码。

点段规范化

URI RFC还定义了如何处理URL中的点段,并提供了一个简单的算法来规范化路径。虽然此功能对于从相对路径引用任何资源至关重要,但它也是许多漏洞的来源。

可以通过利用解析器之间的差异来利用点段规范化,以修改缓存规则的行为并获得特制密钥。即使流行的HTTP服务器(如Apache和Nginx)也完全不同的方式解析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 /<Dynamic_Resource><Encoded_Dot_Segment><Static_Directory>

编码点段很重要。否则,受害者的浏览器将解析它并且不会转发原始恶意路径。Amazon CloudFront、Microsoft Azure和Imperva默认在评估缓存规则之前规范化路径。

利用带规范化的静态目录

当源服务器在映射端点之前规范化路径并且缓存在评估缓存规则之前不规范化路径时,您可以添加一个路径遍历段,该段仅由源服务器处理: GET /<Static_Directory><Encoded_Dot_Segment><Dynamic_Resource>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 /<Dynamic_Resource><Encoded_Dot_Segment><Static_File>

任意Web缓存投毒

当响应被视为静态时,它使用从原始请求派生的密钥存储在缓存中。任何具有相同密钥的未来请求都将使用存储的资源服务。

密钥通常使用URL和主机标头生成。它们可以自定义以使用其他标头或请求元素。

在经典Web缓存投毒中,攻击者尝试使用用户在导航易受攻击网站时请求的URL密钥存储恶意响应。路径访问越频繁,受害者受恶意负载影响越多。您可以在James Kettle的研究《实用Web缓存投毒》和《Web缓存纠缠:投毒的新途径》中阅读更多关于查找Web缓存投毒漏洞的信息。

攻击是有限的,因为在许多情况下,投毒路径不受攻击者控制,并且需要用户交互。例如,考虑一个从未访问过的URL,要么是因为它需要特定参数,如/home?param=XSS,要么是因为路径本身包含负载/

然而,将路径混淆与Web缓存投毒漏洞结合可能允许您修改缓存密钥并投毒高请求资源,如网站主页。在这种情况下,对可以使用的字符没有限制,因为攻击不需要用户交互,这意味着负载可以通过HTTP编辑器/中继器(如Burp Suite)发送。

密钥规范化

规范化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 /<Backend_Path><Path_Traversal><Poisoned_Path>

在此示例中使用双点段,因为负载已经包含斜杠。调整路径遍历以解析到所需的投毒端点。如果对backend_path占位符使用特殊映射,可以应用相同技术。

利用后端分隔符

当字符被源服务器用作分隔符但未被缓存使用时,可以为可缓存资源生成任意密钥。分隔符将阻止后端解析点段。 GET /<Backend_Path><Path_Traversal><Poisoned_Path>

利用前端分隔符

在Web缓存欺骗攻击中,解析差异是由分隔符仅在源服务器中使用但不在缓存中使用引起的。找到对缓存服务器具有特殊含义且可以通过浏览器发送的字符是罕见的。然而,由于Web缓存投毒不需要用户交互,像井号这样的分隔符可以创建路径混淆。这很有用,因为片段被许多HTTP服务器、CDN和后端框架不同地解释,如下表所示:因此,在像Microsoft Azure这样规范化路径并将井号视为分隔符的情况下,可以使用此修改存储资源的缓存密钥: GET /<

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计