HTTP-Request-Smuggling-Part.1 原理

1.概述

纯是为了在现在这个蠢比环境能够自己学习,所以开始逼自己边学边写。

本文不止搬运原文的翻译,将会包括portswigger给准备的实验部分,加深理解。

原文:https://portswigger.net/blog/http-desync-attacks-request-smuggling-reborn

2.原理

以下内容基本来自原文,

自从HTTP/1.1以来,广泛支持通过单个底层TCP或SSL /TLS套接字发送多个HTTP请求。该协议非常简单,只需要将HTTP请求挨着放置,服务器就会解析标头,以确定每个请求的结束位置和下一个开始的位置。

就其本身而言,这是无害的。然而,现代网站由各种系统连接组成,所有系统都通过HTTP进行交流。这种多层体系结构接收来自多个不同用户的HTTP请求,并通过单个TCP/TLS连接进行路由:

(本质其实就是多个用户都请求到前端服务器,比如nginx,然后nginx和后端提供服务的server,是单个TCP/TLS连接的,所以在现在的互联网架构上来看,是非常常见的)

upload successful

这意味着,后端与前端对于处理每个消息结束位置的一致是至关重要的。否则,攻击者可能会发送一条含糊不清的消息,该消息被后端解释为两个不同的HTTP请求:

upload successful

这使攻击者能够在下一个合法用户请求开始时预先添加任意内容。在本文中,被走私的内容将被称为“前缀”,并以橙色突出显示。

让我们假设前端优先考虑第一个内容长度头部,后端优先考虑第二个内容长度头部。从后端的角度来看,TCP流可能看起来像:

upload successful

前端将蓝色和橙色数据转发到后端,后端在发出响应之前读取蓝色内容。这使得后端套接字中毒了橙色数据。当合法的绿色请求到达时,它最终会被附加橙色内容,从而导致意外响应。

在这个例子中,注入的’G’将破坏绿色用户的请求,他们可能会得到“未知方法GPOST”的响应。

本文中的每次攻击都遵循这种基本格式。

在现实生活中,双内容长度技术很少有效,因为许多系统明智地拒绝具有多个内容长度标头的请求。相反,我们将使用分块编码来攻击系统,这次我们根据RFC 2616规范:

If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored.

(如果一个消息同时存在Transfer-Encoding头和Content-Length头,后面的那一个将会被忽略)

由于规范允许使用Transfer-Encoding:chunked和Content-Length处理请求,因此很少有服务器拒绝此类请求。每当我们找到一种方法从系统链中的一个服务器隐藏Transfer-Encoding标头时,它将会去使用Content-Length,那么我们可以使整个系统处理消息不再一致。

大部分人可能不太熟悉分块编码,因为像Burp Suite这样的工具会自动将分块请求/响应,整合成常规消息中以便于编辑。在分块消息中,主体由0个或更多个块组成。每个块由块大小组成,后跟换行符(\r\n),后跟块内容。消息以大小为0的块结束。这是一个简单的使用分块编码去进行同步攻击的例子:

upload successful

我们还没有在这里隐藏Transfer-Encoding标头,所以这个漏洞利用主要适用于前端根本不支持分块编码的系统。

(前端不支持Transfer-Encoding的话,认Content-Length就会多加一个G,然后后端支持,到0结束,G就会被加载后面的请求上)

如果它是不支持分块编码的后端,我们需要翻转偏移量:

upload successful

这种技术适用于很多系统,但我们可以通过使Transfer-Encoding标头稍微难以发现来更多的利用,因为一个系统会忽略这个头。

这可以通过服务器的HTTP解析中的差异来实现。以下是一些请求示例,只有部分服务器识别为Transfer-Encoding:chunked标头。在本研究中,这些中的每一个都已成功用于至少一个系统:

1
Transfer-Encoding: xchunked
1
Transfer-Encoding : chunked
1
2
Transfer-Encoding: chunked
Transfer-Encoding: x
1
Transfer-Encoding:[tab]chunked
1
2
GET / HTTP/1.1
Transfer-Encoding: chunked
1
X: X[\n]Transfer-Encoding: chunked
1
2
Transfer-Encoding
: chunked

如果前端和后端服务器都支持它,那么都是无害的,不然就是一个重要的威胁。

此方面更多的研究请看https://regilero.github.io/tag/Smuggling/

3.方法

请求走私背后的理论是直截了当的,但是不受控制的变量太多同时我们对前端背后发生的事情完全缺乏可见性。

已经开发出了应对这些挑战的技术和工具,并将它们组合成以下简单的方法,我们可以利用这些方法查找请求走私漏洞并证明其影响:

upload successful

4.检测

检测请求走私漏洞的明显方法是发出一个模糊的请求,然后发出正常的“受害者”请求,然后观察后者是否得到意外的响应。但是,这极易受到干扰。

如果另一个用户的请求在我们的受害者请求之前命中,他们将得到损坏的响应,我们将不会发现该漏洞。这意味着在具有大量流量的实时站点上,很难证明请求走私存在而不会在此过程中影响众多真正的用户。即使在没有其他流量的站点上,您也可能会因应用程序级别的怪癖终止连接而导致漏报。

为了解决这个问题,作者开发了一种检测策略,该策略使用一系列消息,这些消息使易受攻击的后端系统挂起并使连接超时。这种技术几乎没有误报,抵制应用程序级别的怪癖,最重要的是几乎没有影响其他用户的风险。

假设前端服务器使用Content-Length头,后端使用Transfer-Encoding头。我将此定位称为CL.TE。我们可以通过发送以下请求来检测潜在的请求走私:

upload successful

由于内容长度较短,前端仅转发蓝色文本,后端将在等待下一个块大小时超时。这将导致可观察到的时间延迟。

如果两个服务器同步(TE.TE或CL.CL),请求将被前端拒绝或由两个系统无害地处理。最后,如果以相反的方式发生(TE.CL),前端将拒绝该消息,而不会将其转发到后端,这要归功于无效的块大小“Q”。这可以防止后端中毒。

我们可以使用以下请求安全地检测TE.CL:

upload successful

如果以相反的方式发生(CL.TE),则此方法将使用X毒化后端套接字,可能会损害合法用户。幸运的是,通过首先运行先前的检测方法,我们可以排除这种可能性。

这些请求可以适应目标解析中的任意差异,并且它们用于通过HTTP Request Smuggler自动识别请求走私漏洞。HTTP Request Smuggler是为帮助此类攻击而开发的开源Burp Suite扩展。它们现在也被用在Burp Suite的核心扫描仪中。虽然这是服务器级漏洞,但单个域上的不同接口通常会路由到不同的目标,因此应将此技术单独应用于每个接口。

(这里总结一下,使用第一个请求方式,超时说明两个服务器为CL.TE,正常响应就是CL.CL,被拒绝就可能是TE.TE或者TE.CL,那么只需要在拒绝的时候,再使用第二个请求,TE.TE就会正常响应,TE.CL就会超时。)

5.确认

目前为止,你已经尽可能地没有给其他用户带来副作用的风险。然而,如果没有进一步的证据,许多客户将不愿意认真对待报告,这也是我们所面临的。展示请求走私的全部潜力的下一步是证明后端中毒是可能的。为此,我们将发出一个目的是在毒化后端的请求,然后发出一个请求,该请求有可能成为中毒的受害者,明显改变响应。

如果第一个请求导致错误,后端服务器可能会关闭连接,丢弃中毒的缓冲区从而破坏攻击。为了避免这种情况,我们可以尝试通过定位一个请求的接口,该接口接受POST请求且保留任何预期的GET/POST参数。

有些站点有多个不同的后端系统,前端会查看每个请求的方法,URL和标头,以决定将其路由到何处。如果受害者请求被路由到攻击请求的不同后端,则攻击将失败。因此,“攻击”和“受害者”请求最初应尽可能相似。

如果目标请求如下所示:

upload successful

然后尝试CL.TE中毒:

upload successful

如果攻击成功,受害者请求(绿色)将得到404响应。

TE.CL攻击看起来很相似,但需要关闭块意味着我们需要自己指定所有头并将受害者请求放在正文中。确保前缀中的Content-Length略大于正文:

upload successful

如果该站点处于活跃状态,则其他用户的请求可能会在您的之前遇到中毒的套接字,这将使您的攻击失败并可能使用户感到不安。因此,此过程通常需要几次尝试,而在高流量站点上可能需要数千次尝试。请谨慎行事,克制并尽可能地定位有问题的服务器。

6.探索

我将使用一系列真实网站演示其余的方法。像往常一样,我专门针对的公司明确表示他们很乐意通过运行bug赏金计划与安全研究人员合作。由于私人程序的激增和昏昏欲睡的补丁时间,我遗憾地不得不编辑很多。如果网站被明确命名,请记住,他们是现在可以抵御此次攻击的少数网站之一。

现在我们已经确定套接字中毒是可能的,下一步是收集信息,以便我们可以发起一个明智的攻击。

前端经常附加并重写HTTP请求标头,如X-Forwarded-Host和X-Forwarded-For以及许多通常具有难以猜测的名称的自定义标头。我们的走私请求可能会丢失这些标头,这可能会导致后端意外的应用程序行为和攻击的失败。

幸运的是,有一个简单的策略,我们可以解除部分隐藏并获得对这些隐藏标题的可见性。那么我们就可以通过自己手动添加标头来恢复功能,甚至可以启用进一步的攻击。

只需在目标应用程序上找到一个返回POST参数的页面,对参数进行随机播放,使反射的参数最后一次,稍微增加Content-Length,然后走私生成的请求:

upload successful

绿色请求将在登陆[email]参数之前由前端重写,因此当它被反射回来时它将泄漏所有内部标题:

upload successful

(这里其实就是在CL.TE的情况下,由于请求的参数会返回在相应页面,中毒的人的请求中的参数就变成了自己原本请求,而且这个请求是前端发给后端的,会带上前端增加的各种头部,注意要加大红字的Content-Length,把后面的内容包裹进去。)

通过递增Content-Length标头,您可以逐渐检索更多信息,直到您尝试读取受害者请求的结尾并超时。

有些系统完全依赖于前端系统的安全性,一旦你过去,你就可以直接跳入。在login.newrelic.com上,’后端’系统是代理自己,所以改变走私的Host头允许我访问不同的New Relic系统。最初,我点击的每个内部系统都认为我的请求是通过HTTP发送的,并通过重定向进行响应:

upload successful

使用前面观察到的X-Forwarded-Proto标头很容易解决这个问题:

upload successful

通过一些内容发现,我在目标上找到了一个有用的接口:

upload successful

错误消息清楚地告诉我,我需要一种某种授权标题,但是却未能给它命名。我决定尝试前面看到的’X-nr-external-service’标题:

upload successful

不幸的是,这不起作用,它导致了我们在尝试直接访问该URL时已经看到的相同的禁止响应。这表明前端正在使用X-nr-external-service标头来表明请求来自互联网,因为我们通过走私丢失了标题,我们无法欺骗他们的系统认为我们的请求来自内部。这非常有教育意义,但并不直接有用,我们仍然需要缺少授权标题的名称。

此时我可以将处理后的请求反射技术应用于一系列接口,直到找到一个具有正确请求头的接口。于是,我查阅我上次破坏New Relic时的笔记并进行欺骗。

https://portswigger.net/blog/cracking-the-lens-targeting-https-hidden-attack-surface

这揭示了两个非常有价值的头,Server-Gateway-Account-Id和Service-Gateway-Is-Newrelic-Admin。使用这些,我能够获得对其内部API的完全管理员级访问权限:

upload successful

New Relic部署了一个修补程序,并将根本原因诊断为F5网关的漏洞。据我所知,没有可用的补丁,这意味着在撰写本文时这仍然是一个0day。

7.利用

直接进入内部API很有用,但它很少见也不是我们唯一的选择。我们还有很多不同的攻击方法可以针对浏览目标网站的每个人。

要确定哪些攻击方法可以应用于其他用户,我们需要了解哪些类型的请求可以中毒。从“确认”阶段重复套接字中毒测试,然后调整“受害者”请求,直到它类似于典型的GET请求。您可能会发现只能使用某些methods,paths或者headers来中断请求。此外,尝试从不同的IP地址发出受害者请求。因为在极少数情况下,您可能会发现您只能中毒来自同一IP的请求。

最后,检查网站是否使用网络缓存,这些可以帮助绕过许多限制,增加我们对哪些资源中毒的控制,并最终增加请求走私漏洞的严重性。

8.商店

如果应用程序支持编辑或存储任何类型的文本数据,则攻击起来非常容易。通过为受害者的请求添加精心设计的存储请求作为前缀,我们可以让应用程序保存他们的请求并将其显示给我们,然后窃取任何身份验证cookie/headers。以下是Trello一个目标的例子,使用了他们的一个文档编辑接口:

upload successful

一旦受害者的请求到达,它将最终保存在我的个人资料中,暴露他们的所有标题和cookie:

upload successful

使用这种技术的唯一主要问题是你将丢失’&’之后发生的任何数据,这使得很难从表单编码的POST请求中窃取正文。我花了一段时间试图通过使用替代请求编码来解决这个限制并最终放弃,但我仍然怀疑它可能以某种方式实现。

数据存储机会并不总是如此明显; 在另一个网站上,我能够使用“联系我们”表格,最终触发包含受害者请求的电子邮件,并获得额外的2,500美元。

9.攻击

能够将任意前缀应用于其他人的响应也开辟了另一种攻击途径:触发有害的响应。

使用有害反应有两种主要方式。最简单的方法是发出“攻击”请求,然后等待别人的请求命中后端套接字并触发有害响应。更棘手但更强大的方法是自己发出“攻击”和“受害者”请求,并希望对受害者请求的有害响应通过Web缓存保存并提供给访问相同URL的任何其他人–web缓存中毒。

(这里富哥应该看不懂,其实就是攻击者伪造受害者的请求,先一步请求并获得有害缓存,当受害者访问时,访问的是中了毒的缓存)

在以下每个请求/响应片段中,黑色文本是对第二个(绿色)请求的响应。对第一个(蓝色)请求的响应被省略,因为它不相关。

10.升级XSS

在审核SaaS应用程序时,Param Miner发现了一个名为SAML的参数,而Burp的扫描仪确认它是个反射XSS。反射XSS本身很好,但是需要用户交互才能大规模利用。

通过请求走私,我们可以将包含XSS的响应提供给随机的人们,主动浏览网站,实现直接的大规模利用。我们还可以访问身份验证标头和Http-Only的cookie,这可能会让我们进一步日其他域名。

upload successful

(POST型反射XSS升级成存储XSS,牛比了嗷)

11.DOM劫持

在寻找与www.redhat.com上的请求走私链接的漏洞时,我发现了一个基于DOM的URL跳转,它提出了一个有趣的挑战:

upload successful

页面上的一些JavaScript是从受害者浏览器的查询字符串中读取’redir’参数,但我怎么能控制它呢?请求走私使我们能够控制服务器认为查询字符串是什么,但受害者浏览器对查询字符串的感知就是他们试图访问的任何页面。

我能够通过服务器端301来解决这个问题:

upload successful

受害者浏览器将收到301重定向到https://www.redhat.com/assets/idx.html?redir=//redat.com@evil.net/,然后执行基于DOM的开放重定向并转到evil.net上。

12.CDN Chaining

一些网站使用多层反向代理和CDN。这为我们提供了额外的机会,这种机会一直受到重视,并且通常也会增加严重性。

一个目标是以某种方式使用了两层Akamai,尽管服务器由同一供应商提供,但可以使它们去同步,从而在受害者网站上提供Akamai网络上任何地方的内容:

upload successful

相同的概念适用于SaaS提供商,通过将请求定向到构建在同一平台上的不同系统,我能够利用建立在众所周知的SaaS平台上的关键网站。

13.无害响应

因为请求走私让我们影响对任意请求的响应,所以一些通常无害的行为变得可利用。例如,即使是简单的开放重定向也可用于通过将JavaScript导入重定向到恶意域来危害帐户。

使用307代码的重定向特别有用,因为在发出POST请求后接收307的浏览器会将POST重新发送到新目标。这可能意味着您可以让不知情的受害者直接将明文密码发送到您的网站。

经典的开放重定向本身很常见,但有一种变体在整个网络中流行,因为它源于Apache和IIS中的默认行为。它很方便地被认为是无害的,几乎每个人都忽略了,因为没有伴随的漏洞,比如请求走私,那么它确实是无用的。如果您尝试访问没有斜杠的文件夹,服务器将使用重定向响应以使用主机头中的主机名附加斜杠:

upload successful

使用此技术时,请密切关注重定向中使用的协议。您可以使用X-Forwarded-SSL之类的头来影响它。如果它停留在HTTP上,并且您正在攻击HTTPS站点,受害者的浏览器将因其混合内容保护而阻止连接。有两个已知的例外情况–1.可以完全绕过Internet Explorer的混合内容保护,2.如果重定向目标位于其HSTS缓存中,Safari将自动升级到HTTPS的连接。

14.Web缓存中毒

在针对特定网站尝试某些基于重定向的攻击几个小时后,我在浏览器中打开了他们的主页以查找更多攻击面并在开发控制台中发现以下错误:

upload successful

无论我从哪台机器加载网站,都会发生此错误,并且IP地址看起来非常熟悉。在我的重定向探测期间,在我的受害者请求之前,其他人对图像文件的请求已经进入,并且缓存保存了中毒的响应。

这是潜在影响的一个很好的证明,但总体上不是一个理想的结果。除了依赖基于超时的检测之外,没有办法完全消除意外缓存中毒的可能性。也就是说,为了将风险降至最低,您可以:

  • 确保“受害者”请求具有缓存。
  • 使用Turbo Intruder尽快发送“受害者”请求。
  • 尝试创建一个前缀,触发带有反缓存标头的响应,或者不太可能缓存的状态代码。
  • 瞄准人们睡了的地理区域的系统。

15.Web缓存欺骗++

如果不是试图减少攻击者/用户混合响应被缓存的可能性,我们会接受它吗?

我们可以尝试使用受害者的Cookie获取包含敏感信息的响应,而不是使用旨在导致有害响应的前缀:

upload successful

前端视角:

upload successful

当用户对静态资源的请求命中中毒缓存时,响应将包含其帐户详细信息,缓存将通过静态资源保存这些信息。然后,我们可以通过从缓存中加载/static/site.js来检索帐户详细信息。

这实际上是Web缓存欺骗攻击的新变种。它在两个关键方面更强大 - 它不需要任何用户交互,也不要求目标站点允许您使用扩展。唯一的问题是攻击者无法确定受害者的响应将落在何处。

(总结一下,这里没什么叼用。)

16.PayPal

随着请求走私链接到缓存中毒,我能够持续劫持许多JavaScript文件。其中一个用于PayPal的登录页面:https://c.paypal.com/webstatic/r/fb/fb-all-prod.pp2.min.js

upload successful

但是,存在一个问题 - PayPal的登录页面使用内容安全策略和脚本src来杀死我的重定向。

upload successful

这最初看起来像是深度防守的胜利。但是,我注意到登录页面在动态生成的iframe中在c.paypal.com上加载了一个子页面。这个子页面没有使用CSP,也导入了我们的中毒JS文件。这使我们可以完全控制iframe的内容,但由于同源策略,我们仍然无法从父页面读取用户的PayPal密码。

upload successful

我的同事Gareth Heyes随后在paypal.com/us/gifts上发现了一个没有使用CSP的页面,还导入了我们中毒的JS文件。通过使用我们的JS将c.paypal.com iframe重定向到该URL(并且第三次触发我们的JS导入),我们终于可以访问父页面并从使用Safari或IE登录的每个人窃取明文PayPal密码。

upload successful

PayPal通过将Akamai配置为拒绝包含Transfer-Encoding:chunked标头的请求并获得18,900美元的赏金,快速解决了此漏洞。

几周之后,在发明和测试一些新的去同步技术时,我决定尝试使用换行标题:

1
2
Transfer-Encoding:
chunked

这似乎使得Transfer-Encoding标头对Akamai完全不可见,Akamai让它通过并再一次授予我对PayPal登录页面的控制权。PayPal迅速应用了更强大的解决方案,并获得了令人印象深刻的20,000美元。