高级请求走私
首先,大部分请求走私都是针对HTTP/1
的,此部分学习基于HTTP/2
的请求走私攻击
-
常见的 HTTP/2
实现如何为请求走私 提供一系列强大的新载体,从而使许多以前安全的站点容易受到此类攻击 -
如何使用请求走私持续毒害响应队列 ,有效地实现全站点接管。 -
如何使用 HTTP/2
独有的输入来构建高严重性漏洞,即使目标根本不重用前端和后端服务器之间的连接
HTTP/2
请求走私
有些时候使用HTTP/2
反而更容易受到网络请求走私的攻击,即使它们以前可以免受此类攻击。
HTTP/2
消息长度
请求走私本质上是利用不同服务器对请求长度的解释之间的差异。HTTP/2
引入了一种单一、强大的机制来实现这一点,长期以来人们一直认为这种机制可以使其固有地免受请求走私的影响。
HTTP/2
消息是通过一系列单独的"帧"通过网络发送的。每个帧前面都有一个明确的长度字段,该字段告诉服务器要读取多少字节。因此,请求的长度是其帧长度的总和。
理论上,只要网站端到端使用 HTTP/2
,这种机制就意味着攻击者没有机会引入请求走私所需的歧义。然而,在现实中,由于 HTTP/2
降级的广泛但危险的做法,情况往往并非如此。
HTTP/2
降级
HTTP/2
降级是使用 HTTP/1
语法重写 HTTP/2
请求以生成等效 HTTP/1
请求的过程。Web
服务器和反向代理通常会这样做,以便在与仅使用HTTP/1
的后端服务器通信时为客户端提供 HTTP/2
支持。此做法是许多攻击的先决条件。
H2.CL
漏洞
HTTP/2
请求不必在标头中明确指定其长度。在降级期间,这意味着前端服务器通常会添加 HTTP/1
标头Content-Length
,并使用 HTTP/2
的内置长度机制得出其值。但在降级之前,这并不总是经过正确验证
前端(HTTP/2)
|
|
---|---|
|
|
|
|
|
|
|
|
GET /admin HTTP/1.1Host: vulnerable-website.comContent-Length: 10x=1
后端 (HTTP/1)
POST /example HTTP/1.1Host: vulnerable-website.comContent-Type: application/x-www-form-urlencodedContent-Length: 0GET /admin HTTP/1.1Host: vulnerable-website.comContent-Length: 10x=1GET / H
实验室:H2.CL
请求走私
要先设置一下属性
第一步,添加Content-Length: 0
为什么呢,前端HTTP /2
,不使用Content-Length
,加上这个之后,就可以在后端进行降级的时候进行走私请求了
前端:不验证Content-Length: 0后端:由于降级,SMUGGLE,走私成功 -> 导致接着的第二次请求404
第二步,将走私的部分换成请求数据包资源/resources
,Host
任意
前端:不验证Content-Length: 0后端:由于降级,GET /resources,走私成功 -> 302跳转到当前请求头的Host下的域名下的资源
第三步,修改服务器/resources
内容,且Host
改为服务器地址
OK
H2.TE
漏洞
分块传输编码与 HTTP/2
不兼容,规范建议任何transfer-encoding: chunked
请求头都应该被删除,如果前端服务器未能做到这一点,并随后降级支持分块编码的 HTTP/1
后端的请求,这也可能引发请求走私攻击。
Front-end (HTTP/2)
|
|
---|---|
|
|
|
|
|
|
|
|
0GET /admin HTTP/1.1Host: vulnerable-website.comFoo: bar
Back-end (HTTP/1)
POST /example HTTP/1.1Host: vulnerable-website.comContent-Type: application/x-www-form-urlencodedTransfer-Encoding: chunked0GET /admin HTTP/1.1Host: vulnerable-website.comFoo: bar
如果某个网站容易受到 H2.CL
或 H2.TE
请求走私的攻击,您可以利用此行为执行我们在之前的请求走私实验室中介绍过的相同攻击。
隐藏的 HTTP/2
支持
就是将中继器的设置中的允许HTTP2
覆盖打开
响应队列中毒
响应队列中毒是一种强大的请求走私攻击,使您能够窃取针对其他用户的任意响应,从而可能危及他们的帐户甚至整个网站。
这是通过走私完整请求来实现的,从而当前端服务器仅期望一个响应时,从后端引出两个响应。
响应队列中毒有何影响?
响应队列中毒的影响通常是灾难性的。一旦队列被中毒,攻击者只需发出任意后续请求即可捕获其他用户的响应。这些响应可能包含敏感的个人或业务数据以及会话令牌等,这实际上授予攻击者对受害者帐户的完全访问权限。
响应队列中毒还会造成严重的附带损害,实际上会破坏通过同一 TCP
连接将流量发送到后端的任何其他用户的站点。在尝试正常浏览站点时,用户将收到来自服务器的看似随机的响应,这将阻止大多数功能正常运行。
如何构造响应队列投毒攻击
为了成功发起响应队列毒化攻击,必须满足以下条件:
-
前端服务器和后端服务器之间的 TCP
连接可重复用于多个请求/响应周期。 -
攻击者能够成功走私完整的独立请求,该请求从后端服务器接收其自己的独特响应。 -
攻击不会导致任何服务器关闭 TCP
连接。服务器通常在收到无效请求时关闭传入连接,因为它们无法确定请求应该在何处结束。
取消响应队列的同步
当你走私一个完整的请求时,前端服务器仍然认为它只转发了一个请求。另一方面,后端会看到两个不同的请求,并将相应地发送两个响应:
前端正确地将第一个响应映射到初始"包装器"请求,并将其转发给客户端。由于没有其他请求等待响应,意外的第二个响应被保存在前端和后端之间的连接队列中。
当前端收到另一个请求时,它会照常将其转发给后端。但是在发出响应时,它会发送队列中的第一个,即被走私请求的剩余响应。
然后,来自后端的正确响应就不再有匹配的请求了。每次有新的请求通过相同的连接转发到后端时,都会重复此循环。
窃取其他用户的回复
一旦响应队列被毒化,攻击者就可以发送任意请求来捕获其他用户的响应。
他们无法控制收到哪些响应,因为他们总是会收到队列中的下一个响应,即对前一个用户请求的响应。在某些情况下,这没什么意义。但是,使用 Burp Intruder
等工具,攻击者可以轻松自动执行重新发出请求的过程。通过这样做,他们可以快速获取针对不同用户的一系列响应,其中至少有一些可能包含有用的数据。
只要前端/后端连接保持打开状态,攻击者就可以继续窃取此类响应。连接关闭的具体时间因服务器而异,但常见的默认设置是在处理完 100
个请求后终止连接。当前连接关闭后,重新建立新连接也很容易。
“
提示
为了更容易区分被盗响应和您自己的请求的响应,请尝试在您发送的两个请求中使用不存在的路径。例如,这样,您自己的请求应该会始终收到 404 响应。
实验室:通过 H2.TE
请求走私进行响应队列投毒
chunked -> 0 -> 走私前缀发送两次 -> 404 说明的确存在走私
然后构造完整的走私请求 -> 反复请求 -> 直到302状态码 -> 获取管理员Cookie如何一直是404状态码 -> 就正常请求10次进行刷新注意: 请求数据包最后两行要有空行 -> 这个应该与后端以 chunked 确定长度有关
通过 CRLF
注入进行请求走私
即使网站采取措施防止基本的 H2.CL
或 H2.TE
攻击,例如验证content-length
或剥离任何transfer-encoding
标头,HTTP/2
的二进制格式也支持一些新颖的方法来绕过这些类型的前端措施。
在 HTTP/1
中,有时可以利用服务器处理独立换行符 (n
) 的方式之间的差异来偷运禁止的标头。如果后端将其视为分隔符,但前端服务器不这样做,则某些前端服务器将根本无法检测到第二个标头。
Foo: barnTransfer-Encoding: chunked
处理完整的 CRLF
(rn
)序列时不存在这种差异,因为所有 HTTP/1
服务器都同意这种终止标头。
另一方面,由于 HTTP/2
消息是二进制的而不是基于文本的,因此每个标头的边界都基于显式、预定的偏移量,而不是分隔符。这意味着rn
在标头值中不再具有任何特殊意义,因此可以将其包含在值本身中而不会导致标头被拆分:
foo: barrnTransfer-Encoding: chunked
这本身似乎相对无害,但当将其重写为 HTTP/1
请求时,rn
将再次被解释为标头分隔符。因此,HTTP/1
后端服务器将看到两个不同的标头:
Foo: barTransfer-Encoding: chunked
实验室:通过 CRLF
注入进行 HTTP/2
请求走私
CRLF注入 -> 需要通过右边的Inspector来进行CRLF注入 -> 添加一个HTTP头 -> 其值为 test[shift+enter换行]加入chunked
出现404 -> 走私成功根据页面中的搜索功能 & 受害者用户每15s访问一次 -> 走私请求 -> 通过search参数 -> 获取受害者请求数据头Repeater发送一次 -> 浏览器刷新一次 -> 如果报错 -> 就一直刷新 -> 直到得到受害者的Cookie信息 -> 如果返回搜索结果 -> 就Repeater重放等待15s之后刷新浏览器
获取到受害者Cookie信息 -> 访问首页抓包 -> 将Cookie信息改为受害者的即可
HTTP/2
独有向量
由于 HTTP/2
是二进制协议而非基于文本的协议,由于其语法的限制,许多潜在的向量无法在 HTTP/1
中构建。
除了CRLF
,再介绍一些可用于注入有效负载的其他 HTTP/2
专用向量。尽管 HTTP/2
规范正式禁止此类请求,但某些服务器无法验证并有效阻止它们。
“
笔记
只能使用 Burp 的 Inspector 面板中的 专门 HTTP/2 功能来执行这些攻击。
通过标头名称注入
在 HTTP/1
中,标头名称不能包含冒号,因为该字符用于向解析器指示名称的结尾。HTTP/2
中并非如此。
通过将冒号与字符rn
组合,您可以使用 HTTP/2
标头的名称字段将其他标头隐藏在前端过滤器之外。一旦使用 HTTP/1
语法重写请求,这些标头将在后端被解释为单独的标头:
前端
foo: barrnTransfer-Encoding: chunkedrnX: ignore
后端
Foo: barrnTransfer-Encoding: chunkedrnX: ignorern
通过伪标头注入
HTTP/2
不使用请求行或状态行。相反,这些数据通过请求前面的一系列"伪标头"传递。在基于文本的 HTTP/2
消息表示中,这些伪标头通常以冒号作为前缀,以帮助将其与普通标头区分开来。总共有五个伪标头:
-
:method
- 请求方法 -
:path
- 请求路径。请注意,这包括查询字符串 -
:authority
- 大致相当于HTTP/1 Host
标头 -
:scheme
- 请求方案,通常是http
或https
-
:status
- 响应状态代码(在请求中未使用)
当网站将请求降级为 HTTP/1
时,它们会使用其中一些伪标头的值来动态构建请求行。这使得构建攻击的一些有趣的新方法成为可能。
提供不明确的主机
尽管 HTTP/1
的Host
标头实际上已被 HTTP/2
中的伪标头:authority
取代,但您仍然可以在请求中发送host
标头。
在某些情况下,这可能会导致重写的 HTTP/1
请求中出现两个Host
标头,这为绕过前端"重复Host
标头"过滤器提供了另一种可能性。这可能会使网站容易受到一系列Host
标头攻击,而这些攻击原本是可以免疫的。
提供不明确的路径
由于请求行的解析方式,在 HTTP/1
中尝试发送具有模糊路径的请求是不可能的。但由于 HTTP/2
中的路径是使用伪标头指定的,因此现在可以发送具有两个不同路径的请求,如下所示:
|
|
---|---|
|
|
|
|
|
|
如果网站访问控制验证的路径与用于路由请求的路径之间存在差异,则这可能会使您能够访问原本禁止访问的端点。
注入完整请求行
在降级期间,伪标头:method
的值将写入生成的 HTTP/1
请求的最开头。如果服务器允许您在:method
值中包含空格,您可能能够注入完全不同的请求行,如下所示:
前端
|
|
---|---|
|
|
|
|
后端
GET /admin HTTP/1.1 /anything HTTP/1.1Host: vulnerable-website.com
只要服务器还能容忍请求行中的任意尾随字符,这就提供了创建具有模糊路径的请求的另一种方法。
注入 URL
前缀
HTTP/2
的另一个有趣功能是能够使用:scheme
伪标头在请求本身中明确指定方案。虽然这通常只包含http
或https
,但您可以包含任意值。
例如,当服务器使用标头:scheme
动态生成 URL
时,这很有用。在这种情况下,您可以向 URL
添加前缀,甚至可以通过将真实 URL
推送到查询字符串来完全覆盖它:
==要求==
|
|
---|---|
|
|
|
|
|
|
==回复==
|
|
---|---|
|
|
在伪头部中注入换行符
当注入:path
或:method
伪标头时,您需要确保生成的 HTTP/1
请求仍然具有有效的请求行。
由于HTTP/1
中的请求行出现rn
就说明终止,因此简单地添加rn
部分请求只会中断请求。降级后,重写的请求必须在rn
您注入的第一个请求之前包含以下序列:
<method> + space + <path> + space + HTTP/1.1
只需想象您的注射在该序列中的位置,并相应地包含所有剩余部分。例如,当注入:path
时,您需要在rn
之前添加一个空格和HTTP/1.1
,如下所示:
前端
|
|
---|---|
|
|
|
|
后端
GET /example HTTP/1.1rnTransfer-Encoding: chunkedrnX: x HTTP/1.1rnHost: vulnerable-website.comrnrn
请注意,在这种情况下,我们还添加了任意尾随标头来捕获重写过程中自动添加的空格和协议。
HTTP/2
请求拆分
当我们研究响应队列中毒时,您了解了如何在后端将单个 HTTP
请求拆分为两个完整的请求。在我们研究的示例中,拆分发生在消息正文内部,但是当 HTTP/2
降级时,您也可以使此拆分发生在标头中。
这种方法更加通用,因为您不依赖于使用允许包含主体的请求方法。例如,您甚至可以使用GET
请求:
|
|
---|---|
|
|
|
|
|
|
Content-length
在验证并且后端不支持分块编码的情况下这也很有用。
考虑前端重写
要拆分标头中的请求,您需要了解前端服务器如何重写请求,并在手动添加任何 HTTP/1
标头时考虑到这一点。否则,其中一个请求可能会缺少必需的标头。
例如,您需要确保后端收到的两个请求都包含Host
标头。降级期间,前端服务器通常会剥离:authority
伪标头并将其替换为新的 HTTP/1
标头。执行此操作的方法有很多种,这可能会影响您需要将要注入的标头Host
定位在何处。
考虑以下请求:
|
|
---|---|
|
|
|
|
|
|
在重写过程中,一些前端服务器会将新Host
标头附加到当前标头列表的末尾。就 HTTP/2
前端而言,这位于foo
标头之后。请注意,这也是在后端拆分请求的点之后。这意味着第一个请求根本没有Host
标头,而被走私的请求将有两个。在这种情况下,您需要定位注入的Host
标头,以便它在拆分发生后最终出现在第一个请求中:
|
|
---|---|
|
|
|
|
|
|
您还需要以类似的方式调整要注入的任何内部标题的位置。
实验室:通过 CRLF
注入拆分 HTTP/2
请求
用Bp的 Inspector功能 添加HTTP头 -> 走私完整请求 -> 获取受害者的Cookie
HTTP
请求隧道
我们介绍的许多请求走私攻击都是因为前端和后端之间的同一连接处理多个请求而发生的。虽然有些服务器会重复使用连接来处理任何请求,但其他服务器的策略更为严格。
例如,某些服务器仅允许来自同一 IP
地址或同一客户端的请求重用连接。其他服务器则根本不会重用连接,这限制了您通过传统请求走私所能实现的目标,因为您没有明显的方式来影响其他用户的流量。
尽管您无法毒害套接字来干扰其他用户的请求,但您仍然可以发送一个请求,该请求将引发来自后端的两个响应。这可能使您能够完全隐藏来自前端的请求及其匹配的响应。
您可以使用此技术绕过可能阻止您发送某些请求的前端安全措施。事实上,即使是一些专门为防止请求走私攻击而设计的机制也无法阻止请求隧道。
以这种方式将请求隧道传输到后端提供了一种更有限的请求走私形式,但在好心人手中仍然可能导致高严重性的漏洞。
使用 HTTP/2
进行请求隧道
HTTP/1
和 HTTP/2
都可以进行请求隧道传输,但在仅使用 HTTP/1
的环境中检测起来要困难得多。由于HTTP/1
中持久 (keep-alive
) 连接的工作方式,即使您确实收到了两个响应,也不一定能确认请求已成功走私。
另一方面,在 HTTP/2
中,每个"流"应该只包含一个请求和响应。如果您收到一个 HTTP/2
响应,其正文看起来像是 HTTP/1
响应,那么您可以确信您已成功隧道传输第二个请求。
通过 HTTP/2
请求隧道泄露内部标头
当请求隧道是您唯一的选择时,您将无法使用我们在早期实验中介绍的技术来泄漏内部标头,但 HTTP/2
降级可以实现替代解决方案。
您可以诱使前端将内部标头附加到将成为后端主体参数的内容中。假设我们发送一个如下所示的请求:
|
|
---|---|
|
|
|
|
|
|
|
|
|
在这种情况下,前端和后端都同意只有一个请求。有趣的是,它们可以对标头的结束位置产生分歧。
前端将我们注入的所有内容视为标头的一部分,因此会在尾随comment=
字符串后添加任何新标头。另一方面,后端看到该rnrn
序列并认为这是标头的结尾。该comment=
字符串与内部标头一起被视为正文的一部分。结果是一个comment
以内部标头为值的参数。
POST /comment HTTP/1.1Host: vulnerable-website.comContent-Type: application/x-www-form-urlencodedContent-Length: 200comment=X-Internal-Header: secretContent-Length: 3x=1
盲请求隧道
一些前端服务器会读取从后端收到的所有数据。这意味着,如果您成功隧道化请求,它们可能会将两个响应转发给客户端,而对隧道化请求的响应则嵌套在主响应的主体内。
其他前端服务器仅读取Content-Length
响应标头中指定的字节数,因此只有第一个响应会转发到客户端。这会导致盲请求隧道漏洞,因为您将无法看到隧道请求的响应。
使用 HEAD
进行非盲请求隧道传输
HEAD
盲请求隧道可能很难被利用,但你偶尔可以通过使用请求使这些漏洞变得非盲目。
对请求的响应HEAD
通常包含content-length
标头,即使它们没有自己的主体。这通常是指对同一端点的GET
请求将返回的资源的长度。某些前端服务器无法考虑到这一点,并会尝试读取标头中指定的字节数。如果您成功通过执行此操作的前端服务器传输请求,此行为可能会导致它过度读取来自后端的响应。因此,您收到的响应可能包含从对隧道请求的响应开始的字节。
要求
|
|
---|---|
|
|
|
|
|
|
回复
|
|
---|---|
|
|
|
|
HTTP/1.1 200 OKContent-Type: text/htmlContent-Length: 4286<!DOCTYPE html><h1>Tunnelled</h1><p>This is a tunnelled respo
由于您实际上将content-length
一个响应的标头与另一个响应的主体混合在一起,因此成功使用这种技术需要一点平衡行为。
如果您向其发送请求的端点HEAD
返回的资源比您尝试读取的隧道响应短,则它可能会在您看到任何有趣的内容之前被截断,如上例所示。另一方面,如果返回的内容content-length
比隧道请求的响应长,您可能会遇到超时,因为前端服务器正在等待来自后端的其他字节到达。
幸运的是,经过反复试验,您通常可以使用以下解决方案之一来克服这些问题:
-
将您的 HEAD
请求指向不同的端点,根据需要返回更长或更短的资源。 -
如果资源太短,请在主 HEAD
请求中使用反射输入来注入任意填充字符。即使您实际上看不到输入被反射,返回的内容content-length
仍会相应增加。 -
如果资源太长,请使用隧道请求中的反射输入注入任意字符,以便隧道响应的长度匹配或超过预期内容的长度。
实验室:通过 HTTP/2
请求隧道绕过访问控制
添加一个HTTP头进行测试 -> Host随意写入 -> 返回504 -> 存在CRLF注入
测试搜索功能 -> 发现响应中带回搜索信息构造恶意HTTP头 -> 确保左边Body区域的长度大于 构造的Content-Length长度cookie: session=HhZFkaooOAZup8Tdb5Nb2H0pNjtZsvqo; b-user-id=7c25b13a-a9e8-8958-5ec3-7c69f46bfb3fX-SSL-VERIFIED: 0X-SSL-CLIENT-CN: nullX-FRONTEND-KEY: 9964404396666299
改为HEAD请求方式 -> 响应多大重新构造隧道请求报500的错误 -> 可以将 :path 伪标头改一下 -> /login -> 可能还是报错可以直接请求删除carlos的路径 -> 虽然报错 -> 但是已经请求成功了
通过 HTTP/2
请求隧道进行 Web
缓存投毒
尽管请求隧道通常比传统的请求走私更受限制,但有时您仍然可以构造高严重性攻击。例如,您可以将我们迄今为止研究过的请求隧道技术结合起来,形成一种超强大的 Web
缓存投毒形式。
使用非盲请求隧道,您可以有效地将一个响应的标头与另一个响应的正文混合搭配。如果正文中的响应反映了未编码的用户输入,您可能能够在浏览器通常不会执行代码的上下文中利用此行为来实施反射型 XSS
。
例如,以下响应包含未编码的、攻击者可控制的输入:
HTTP/1.1 200 OKContent-Type: application/json{ "name" : "test<script>alert(1)</script>" }[etc.]
就其本身而言,这相对无害。这Content-Type
意味着此有效负载将简单地被浏览器解释为 JSON
。但请考虑一下,如果您将请求隧道传输到后端会发生什么。此响应将出现在不同响应的主体内,有效地继承其标头,包括content-type
。
|
|
---|---|
|
|
|
|
HTTP/1.1 200 OKContent-Type: application/json{ "name" : "test<script>alert(1)</script>" }[etc.]
由于缓存发生在前端,因此缓存也可能被欺骗为其他用户提供这些混合响应。
原文始发于微信公众号(夜风Sec):HTTP高级走私请求攻击
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论