HTTP request smuggling
在本节中,我们将解释什么是 HTTP 请求走私,并描述常见的请求走私漏洞是如何产生的。
什么是 HTTP 请求走私
HTTP 请求走私是一种干扰网站处理多个 HTTP 请求序列的技术。请求走私漏洞危害很大,它使攻击者可以绕过安全控制,未经授权访问敏感数据并直接危害其他应用程序用户。
HTTP 请求走私到底发生了什么
现在的应用架构中经常会使用诸如负载均衡、反向代理、网关等服务,这些服务在链路上起到了一个转发请求给后端服务器的作用,因为位置位于后端服务器的前面,所以本文把他们称为前端服务器。
当前端服务器(转发服务)将 HTTP 请求转发给后端服务器时,它通常会通过与后端服务器之间的同一个网络连接发送多个请求,因为这样做更加高效。协议非常简单:HTTP 请求被一个接一个地发送,接受请求的服务器则解析 HTTP 请求头以确定一个请求的结束位置和下一个请求的开始位置,如下图所示:
在这种情况下,前端服务器(转发服务)与后端系统必须就请求的边界达成一致。否则,攻击者可能会发送一个模棱两可的请求,该请求被前端服务器(转发服务)与后端系统以不同的方式解析:
如上图所示,攻击者使上一个请求的一部分被后端服务器解析为下一个请求的开始,这时就会干扰应用程序处理该请求的方式。这就是请求走私攻击,其可能会造成毁灭性的后果。
HTTP 请求走私漏洞是怎么产生的
绝大多数 HTTP 请求走私漏洞的出现是因为 HTTP 规范提供了两种不同的方法来指定请求的结束位置:Content-Length
头和 Transfer-Encoding
头。
Content-Length
头很简单,直接以字节为单位指定消息体的长度。例如:
POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11
q=smuggling
Transfer-Encoding
头则可以声明消息体使用了 chunked
编码,就是消息体被拆分成了一个或多个分块传输,每个分块的开头是当前分块大小(以十六进制表示),后面紧跟着 \r\n
,然后是分块内容,后面也是 \r\n
。消息的终止分块也是同样的格式,只是其长度为零。例如:
POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked
b
q=smuggling
0
由于 HTTP 规范提供了两种不同的方法来指定 HTTP 消息的长度,因此单个消息中完全可以同时使用这两种方法,从而使它们相互冲突。HTTP 规范为了避免这种歧义,其声明如果 Content-Length
和 Transfer-Encoding
同时存在,则 Content-Length
应该被忽略。当只有一个服务运行时,这种歧义似乎可以避免,但是当多个服务被连接在一起时,这种歧义就无法避免了。在这种情况下,出现问题有两个原因:
- 某些服务器不支持请求中的
Transfer-Encoding
头。 - 某些服务器虽然支持
Transfer-Encoding
头,但是可以通过某种方式进行混淆,以诱导不处理此标头。
如果前端服务器(转发服务)和后端服务器处理 Transfer-Encoding
的行为不同,则它们可能在连续请求之间的边界上存在分歧,从而导致请求走私漏洞。
如何进行 HTTP 请求走私攻击
请求走私攻击需要在 HTTP 请求头中同时使用 Content-Length
和 Transfer-Encoding
,以使前端服务器(转发服务)和后端服务器以不同的方式处理该请求。具体的执行方式取决于两台服务器的行为:
CL.TE
:前端服务器(转发服务)使用Content-Length
头,而后端服务器使用Transfer-Encoding
头。TE.CL
:前端服务器(转发服务)使用Transfer-Encoding
头,而后端服务器使用Content-Length
头。TE.TE
:前端服务器(转发服务)和后端服务器都使用Transfer-Encoding
头,但是可以通过某种方式混淆标头来诱导其中一个服务器不对其进行处理。
CL.TE 漏洞
前端服务器(转发服务)使用 Content-Length
头,而后端服务器使用 Transfer-Encoding
头。我们可以构造一个简单的 HTTP 请求走私攻击,如下所示:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
前端服务器(转发服务)使用 Content-Length
确定这个请求体的长度是 13 个字节,直到 SMUGGLED
的结尾。然后请求被转发给了后端服务器。
后端服务器使用 Transfer-Encoding
,把请求体当成是分块的,然后处理第一个分块,刚好又是长度为零的终止分块,因此直接认为消息结束了,而后面的 SMUGGLED
将不予处理,并将其视为下一个请求的开始。
TE.CL 漏洞
前端服务器(转发服务)使用 Transfer-Encoding
头,而后端服务器使用 Content-Length
头。我们可以构造一个简单的 HTTP 请求走私攻击,如下所示:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
注意:上面的 0 后面还有 \r\n\r\n
。
前端服务器(转发服务)使用 Transfer-Encoding
将消息体当作分块编码,第一个分块的长度是 8 个字节,内容是 SMUGGLED
,第二个分块的长度是 0 ,也就是终止分块,所以这个请求到这里终止,然后被转发给了后端服务。
后端服务使用 Content-Length
,认为消息体只有 3 个字节,也就是 8\r\n
,而剩下的部分将不会处理,并视为下一个请求的开始。
TE.TE 混淆 TE 头
前端服务器(转发服务)和后端服务器都使用 Transfer-Encoding
头,但是可以通过某种方式混淆标头来诱导其中一个服务器不对其进行处理。
混淆 Transfer-Encoding
头的方式可能无穷无尽。例如:
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
漏洞相同。
如何防御 HTTP 请求走私漏洞
当前端服务器(转发服务)通过同一个网络连接将多个请求转发给后端服务器,且前端服务器(转发服务)与后端服务器对请求边界存在不一致的判定时,就会出现 HTTP 请求走私漏洞。防御 HTTP 请求走私漏洞的一些通用方法如下:
- 禁用到后端服务器连接的重用,以便每个请求都通过单独的网络连接发送。
- 对后端服务器连接使用 HTTP/2 ,因为此协议可防止对请求之间的边界产生歧义。
- 前端服务器(转发服务)和后端服务器使用完全相同的 Web 软件,以便它们就请求之间的界限达成一致。
在某些情况下,可以通过使前端服务器(转发服务)规范歧义请求或使后端服务器拒绝歧义请求并关闭网络连接来避免漏洞。然而这种方法比上面的通用方法更容易出错。
查找 HTTP 请求走私漏洞
在本节中,我们将介绍用于查找 HTTP 请求走私漏洞的不同技术。
计时技术
检测 HTTP 请求走私漏洞的最普遍有效的方法就是计时技术。发送请求,如果存在漏洞,则应用程序的响应会出现时间延迟。
使用计时技术查找 CL.TE 漏洞
如果应用存在 CL.TE
漏洞,那么发送如下请求通常会导致时间延迟:
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 4
1
A
X
前端服务器(转发服务)使用 Content-Length
认为消息体只有 4 个字节,即 1\r\nA
,因此后面的 X
被忽略了,然后把这个请求转发给后端。而后端服务使用 Transfer-Encoding
则会一直等待终止分块 0\r\n
。这就会导致明显的响应延迟。
使用计时技术查找 TE.CL 漏洞
如果应用存在 TE.CL
漏洞,那么发送如下请求通常会导致时间延迟:
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 6
0
X
前端服务器(转发服务)使用 Transfer-Encoding
,由于第一个分块就是 0\r\n
终止分块,因此后面的 X
直接被忽略了,然后把这个请求转发给后端。而后端服务使用 Content-Length
则会一直等到后续 6 个字节的内容。这就会导致明显的延迟。
注意:如果应用程序易受 CL.TE
漏洞的攻击,则基于时间的 TE.CL
漏洞测试可能会干扰其他应用程序用户。因此,为了隐蔽并尽量减少干扰,你应该先进行 CL.TE
测试,只有在失败了之后再进行 TE.CL
测试。
使用差异响应确认 HTTP 请求走私漏洞
当检测到可能的请求走私漏洞时,可以通过利用该漏洞触发应用程序响应内容的差异来获取该漏洞进一步的证据。这包括连续向应用程序发送两个请求:
- 一个攻击请求,旨在干扰下一个请求的处理。
- 一个正常请求。
如果对正常请求的响应包含预期的干扰,则漏洞被确认。
例如,假设正常请求如下:
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
漏洞,你可以发送如下攻击请求:
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
如果攻击成功,则最后两行会被后端服务视为下一个请求的开头。这将导致紧接着的一个正常的请求变成了如下所示:
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
漏洞,你可以发送如下攻击请求:
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
如果攻击成功,则后端服务器将从 GET / 404
以后的所有内容都视为属于收到的下一个请求。这将会导致随后的正常请求变为:
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 的响应,这表明攻击请求确实产生了干扰。
注意,当试图通过干扰其他请求来确认请求走私漏洞时,应记住一些重要的注意事项:
- “攻击”请求和“正常”请求应该使用不同的网络连接发送到服务器。通过同一个连接发送两个请求不会证明该漏洞存在。
- “攻击”请求和“正常”请求应尽可能使用相同的URL和参数名。这是因为许多现代应用程序根据URL和参数将前端请求路由到不同的后端服务器。使用相同的URL和参数会增加请求被同一个后端服务器处理的可能性,这对于攻击起作用至关重要。
- 当测试“正常”请求以检测来自“攻击”请求的任何干扰时,您与应用程序同时接收的任何其他请求(包括来自其他用户的请求)处于竞争状态。您应该在“攻击”请求之后立即发送“正常”请求。如果应用程序正忙,则可能需要执行多次尝试来确认该漏洞。
- 在某些应用中,前端服务器充当负载均衡器,根据某种负载均衡算法将请求转发到不同的后端系统。如果您的“攻击”和“正常”请求被转发到不同的后端系统,则攻击将失败。这是您可能需要多次尝试才能确认漏洞的另一个原因。
- 如果您的攻击成功地干扰了后续请求,但这不是您为检测干扰而发送的“正常”请求,那么这意味着另一个应用程序用户受到了您的攻击的影响。如果您继续执行测试,这可能会对其他用户产生破坏性影响,您应该谨慎行事。
利用 HTTP 请求走私漏洞
在本节中,我们将描述 HTTP 请求走私漏洞的几种利用方法,这也取决于应用程序的预期功能和其他行为。
利用 HTTP 请求走私漏洞绕过前端服务器(转发服务)安全控制
在某些应用程序中,前端服务器(转发服务)不仅用来转发请求,也用来实现了一些安全控制,以决定单个请求能否被转发到后端处理,而后端服务认为接受到的所有请求都已经通过了安全验证。
假设,某个应用程序使用前端服务器(转发服务)来做访问控制,只有当用户被授权访问的请求才会被转发给后端服务器,后端服务器接受的所有请求都无需进一步检查。在这种情况下,可以使用 HTTP 请求走私漏洞绕过访问控制,将请求走私到后端服务器。
假设当前用户可以访问 /home
,但不能访问 /admin
。他们可以使用以下请求走私攻击绕过此限制:
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
两个单独的请求,并且认为请求都通过了权限验证,此时 /admin
的访问控制实际上就被绕过了。
前端服务器(转发服务)对请求重写
在许多应用程序中,请求被转发给后端服务之前会进行一些重写,通常是添加一些额外的请求头之类的。例如,转发请求重写可能:
- 终止 TLS 连接并添加一些描述使用的协议和密钥之类的头。
- 添加
X-Forwarded-For
头用来标记用户的 IP 地址。 - 根据用户的会话令牌确定用户 ID ,并添加用于标识用户的头。
- 添加一些其他攻击感兴趣的敏感信息。
在某些情况下,如果你走私的请求缺少一些前端服务器(转发服务)添加的头,那么后端服务可能不会正常处理,从而导致走私请求无法达到预期的效果。
通常有一些简单的方法可以准确地得知前端服务器(转发服务)是如何重写请求的。为此,需要执行以下步骤:
- 找到一个将请求参数的值反映到应用程序响应中的 POST 请求。
- 随机排列参数,以使反映的参数出现在消息体的最后。
- 将这个请求走私到后端服务器,然后直接发送一个要显示其重写形式的普通请求。
假设应用程序有个登录的功能,其会反映 email 参数:
POST /login HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
[email protected]
响应内容包括:
此时,你可以使用以下请求走私攻击来揭示前端服务器(转发服务)对请求的重写:
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 参数的值,且在响应中反映出来: