HTTP请求走私漏洞

什么是HTTP请求走私

HTTP请求走私攻击,顾名思义,就会像走私一样在一个HTTP请求包中夹带另一个或多个HTTP请求包,在前端看来是一个HTTP请求包,但是到了后端可能会被解析器分解开从而导致夹带的HTTP请求包也会被解析,最终可以导致未授权访问敏感数据或攻击其他用户。

原理

keep-alive 与 pipeline

为了缓解源站的压力,一般会在用户和后端服务器(源站)之间加设前置服务器,用以缓存、简单校验、负载均衡等,而前置服务器与后端服务器往往是在可靠的网络域中,ip 也是相对固定的,所以可以重用 TCP 连接来减少频繁 TCP 握手带来的开销。这里就用到了 HTTP1.1 中的 Keep-Alive 和Pipeline 特性:

所谓 Keep-Alive,就是在 HTTP 请求中增加一个特殊的请求头 Connection: Keep-Alive,告诉服务器,接收完这次 HTTP 请求后,不要关闭 TCP 链接,后面对相同目标服务器的 HTTP 请求,重用这一个 TCP 链接,这样只需要进行一次 TCP 握手的过程,可以减少服务器的开销,节约资源,还能加快访问速度。这个特性在 HTTP1.1 中是默认开启的。

有了 Keep-Alive 之后,后续就有了 Pipeline,在这里呢,客户端可以像流水线一样发送自己的HTTP 请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端。现如今,浏览器默认是不启用 Pipeline的,但是一般的服务器都提供了对 Pipleline 的支持。

当我们向代理服务器发送一个比较模糊的 HTTP 请求时,由于两者服务器的实现方式不同,可能代理服务器认为这是一个 HTTP 请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了 HTTP 走私攻击。

  • pipline

在1个Tcp连接中发送多个请求

  • Content-Length

HTTP包的一个标头,用来指明发送给接收方的消息的大小

  • Transfer-Encoding

传输编码

漏洞成因

大多数HTTP请求走私漏洞的出现是因为HTTP规范提供了两种不同的方法指定请求的结束位置。

Content-Length头以字节为单位指定消息正文的长度,例如:

1
2
3
4
5
6
POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 11

q=smuggling

Transfer-Encoding头一般指定正文使用分块编码,例如:

1
2
3
4
5
6
7
8
POST /search HTTP/1.1
Host: normal-website.com
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

b
q=smuggling
0

每个块之间以换行符(\r\n)分割开,直到块大小为0字节时视为正文的结束,就是因为这两种不同的方法来指定HTTP消息的长度,就导致如果同时使用这两个头会造成冲突,HTTP规范中规定,如果两个头同时存在则忽略Content-Length头。

HTTP请求走私的几种方式

根据前后端判断数据包是否结束的http头不同,可以分为如下三种情况

CL.TE:前端服务器使用Content-Lengthheader,后端服务器使用Transfer-Encodingheader。
TE.CL:前端服务器使用Transfer-Encodingheader,后端服务器使用Content-Lengthheader。
TE.TE:前端服务器和后端服务器都支持Transfer-Encodingheader,但是可以通过某种方式混淆header来诱导其中一台服务器不处理它。

CL.TE

前置服务器认为 Content-Length 优先级更高(或者根本就不支持 Transfer-Encoding ) ,后端认为Transfer-Encoding 优先级更高。

例如:

1
2
3
4
5
6
7
8
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 13
Transfer-Encoding: chunked

0\r\n
\r\n
SMUGGLED

从上面这一个HTTP请求包来看,CL头设置的是13,即从正文开始算包含13个字节的内容为止算是一个请求包,但是当这个请求包发到后端服务器时会采用TE头来处理请求包,此时会因为0的下一行是空行而认为该请求包已经结束了,那么多出来的内容怎么办呢?会被认为是下一个请求包的开始,此时则会产生HTTP请求走私攻击。

练习靶场:使用portswigger提供的靶场进行学习。

image-20231106202608836

CL头的值为6,就是包含三行共六个字节(包括换行符),TE头指定了使用分块编码,发送两次请求包,成功因为前后端处理方式不同而导致HTTP走私攻击。

TE.CL漏洞

这一种就是前端服务器使用TE头处理,而后端服务器使用CL头处理,我们参考如下示例

1
2
3
4
5
6
7
8
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0

原理就是前端服务器通过使用TE头指定的分块编码来分割处理请求包,既然是分块编码,就得指定每个分块的大小,就如上述代码所示,第一个分块大小为8字节长,第二个分块大小为0,分块编码会一直读取直到分块大小为0,所以以上的请求包会被前端当成一个请求包转发到后端服务器,但是到了后端服务器会因为CL头指定的长度仅包括了8及后面的CLRF字符而将这个请求包分割成两个处理,这就导致了HTTP请求走私漏洞。

练习靶场:使用portswigger提供的靶场进行学习。

image-20231106202627965

这里有一个需要注意的,就是在0的后面要添加两个空行,然后就也会因为前后端对HTTP请求方式不同而导致HTTP走私攻击

TE.TE漏洞:混淆TE头

现在的场景是虽然前后端都支持TE头,但是可以通过某种混淆手段让某一端不处理TE头,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

image-20231106204806322

我们看到有两个TE头,但是有一个TE头是做了混淆的,所以就会导致在后端的时候不使用TE头来处理此时会转而采用CL头处理,从而将一个HTTP请求拆分成两个,导致HTTP走私攻击。

漏洞发现

利用延时方式发现

利用服务器响应延时方式发现CL.TE漏洞

当同时存在CL头和TE头时,如果请求包正文的长度大于CL头指定的长度,则会导致请求包只有CL头指定长度内的内容,从而导致后端服务器因为采用TE头处理而一直等待后续请求包,最终会导致超时,故可以证明存在CL.TE漏洞,例如

1
2
3
4
5
6
7
8
POST / HTTP/1.1
Host: vulnerable-website.com
Transfer-Encoding: chunked
Content-Length: 4

1
A
X

漏洞利用

练习靶场:使用portswigger提供的靶场进行学习。

实验的最终目的是获取其他用户的Cookie用来访问其他账号。

我们首先去寻找一个能够将传入的信息存储到网站中的POST请求表单,很容易就能发现网站中有一个用户评论的地方。

抓取POST请求并构造数据包

漏洞修复

禁用后端连接的重用,防止多个请求拼接

使用HTTP/2用于后端连接,该协议可以防止请求之间的界限模糊不清

前后端使用完全相同的中间件,以保证对请求处理方式的一致性

参考链接

https://www.anquanke.com/post/id/246516