前言
打比赛碰到了这个知识点,就顺便学习一下,http走私这块如果要考起来难度还是比较高的。
漏洞成因
http走私与其他web漏洞存在着比较大的不同,因为不同服务器对RFC标准实现的方式与程度都不尽相同,这就导致了对同一个http请求不同服务器可能会有不同的处理结果,因此http走私的payload并不相通。
接下来我们来介绍导致http走私漏洞的两个http1.1协议特性,Keep-Alive
和Pipeline
:
所谓Keep-Alive,就是在HTTP请求中增加一个特殊的请求头Connection: Keep-Alive,告诉服务器,接收完这次HTTP请求后,不要关闭TCP链接,后面对相同目标服务器的HTTP请求,重用这一个TCP链接,这样只需要进行一次TCP握手的过程,可以减少服务器的开销,节约资源,还能加快访问速度。当然,这个特性在HTTP1.1中是默认开启的。
有了Keep-Alive之后,后续就有了Pipeline,在这里呢,客户端可以像流水线一样发送自己的HTTP请求,而不需要等待服务器的响应,服务器那边接收到请求后,需要遵循先入先出机制,将请求和响应严格对应起来,再将响应发送给客户端。
简单来说就是,长连接允许多个请求复用同一个tcp连接,不必每个请求重新建立连接,大大节约了服务器资源,pipline使得http的请求不必等到响应之后在发起,而可以流式得发起请求,请求到达服务端后仍然通过排队的方式进行处理。
当我们向代理服务器发送一个比较模糊的HTTP请求时,由于服务器的实现方式不同(处理te与cl时存在差异),可能代理服务器认为这是一个HTTP请求,然后将其转发给了后端的源站服务器,但源站服务器经过解析处理后,只认为其中的一部分为正常请求,剩下的那一部分,就算是走私的请求,当该部分对正常用户的请求造成了影响之后,就实现了HTTP走私攻击。
请求方式
cl!=0
在RFC7231中提到,假设前端代理服务器允许GET请求携带请求体,而后端服务器不允许GET请求携带请求体,它会直接忽略掉GET请求中的Content-Length头,不进行处理。
这种情况下就有可能会导致走私,比如我们构造如下的请求:
1 | GET / HTTP/1.1\r\n |
前端服务器收到请求后,读取Content-Length判断这是一个完整请求,然后转发到后端服务器,后端服务器不处理Content-Length,但又由于Pipeline存在,这就意味着后端认为接收到了两个请求,导致了走私的发生。
cl-cl
RFC7230中规定,当服务器收到的请求中含有两个Content-Length且值不相等时应该返回400。但是总是有一些服务器会存在不严格执行标准的情况,在这样的情况下我们就可以安排走私了。
首先构造一个恶意请求:
1 | POST / HTTP/1.1\r\n |
在这种情况下,前端读取到了cl是8,因此整个包被送入了后端,但是后端cl只读取了7,因此缓冲区此时剩下一个字母a。那么此时如果一个正常用户对服务器发起了请求:
1 | GET /index.html HTTP/1.1\r\n |
原来存在于缓冲区的字母a就被拼接了,实际请求发生了改变:
1 | aGET /index.html HTTP/1.1\r\n |
这样就可以插入一些恶意语句,拓展为CSRF等等。
cl-te
简单来说,这种走私方式利用了前端服务器只处理Content-Length,后端只处理Transfer-Encoding这一情况,而关于Transfer-Encoding这一请求头可以参考下面这篇文章:
HTTP 协议中的 Transfer-Encoding
这其中我们要只要的是chunked编码的格式(size用hex表示):
1 | [chunk size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk data][\r\n][chunk size = 0][\r\n][\r\n] |
接下来我们可以构造如下请求:
1 | POST / HTTP/1.1\r\n |
这样,由于前端服务器解析了Content-Length,因此
1 | 0\r\n |
被全部解析,接下来传输到后端后后端服务器解析Transfer-Encoding,那么识别到G之前就被认为已经结束了,因此G被留在了缓冲区,那么就会导致下一次请求G被解析出来拼接到请求之前,导致报错。
te-cl
看名字应该也能理解,就是和cl-te相反,前端服务器解析Transfer-Encoding,而后端服务器解析Content-Length。那么我们构建这样一个请求:
1 | POST / HTTP/1.1\r\n |
我们发现首先前端服务器处理Transfer-Encoding,那么直到读取到0\r\n\r\n
结束,因此整个请求都是完整的,这样整个请求就被送到了后方服务器。接下来后端服务器解析Content-Length,那么读取完21\r\n
后就结束了,后面的内容就被当做了另一个请求继续执行。
te-te
那么排列组合一下剩下的最后一种情况就是te-te了,前后端服务器都会解析处理Transfer-Encoding。不过有所区别的是,由于前后端服务器毕竟不是同一种,我们无法直接构造请求。这里我们可以使用Content-Length加以混淆从而使服务器不处理某个Transfer-Encoding。这样就相当于把问题又变成了te-cl或者cl-te。
那么我们构造请求:
1 | POST / HTTP/1.1\r\n |
这里后端服务器就不会解析Transfer-Encoding了,而是解析处理Content-length,达到了te-cl的走私效果。
[RoarCTF 2019]Easy Calc
这道题我们之前是利用php字符串解析漏洞解决的,那么现在我们利用http走私的方法。
首先放下代码calc.php:
1 | <?php |
其实字母都是不允许输入的,会返回403,不过我们可以利用cl-cl的http请求走私来绕过waf。
构造如下请求:
1 | GET /calc.php?num=phpinfo(); HTTP/1.1 |
由于前后端服务器分别解析了Content-Length,导致服务器以为我们没有输入内容,返回了400,但是phpinfo依然成功显示。因此这样我们只需要绕过明面的blacklist就可以了,最终payload:
1 | GET /calc.php?num=var_dump(readfile(chr(47).base_convert(25254448,10,36))); HTTP/1.1 |
构造这个请求获得flag。
总结
比赛里的那题http请求走私难度还是挺大的,等之后可以复现一下,也了解一下http走私在比赛中的利用方法,理论写起来还是比较空泛的,最后一定要落到实践上。