HRS 请求走私 学习记录

前言:
其实http 请求走私,去年火起来的时候就有了解了,也自己看过一些portswigger 上面的视频和文章,但是没有自己动手来实践,这次来自己动手来实践一下。

0x01 什么是请求走私:

HTTP请求走私是一种干扰网站处理从一个或多个用户接收的HTTP请求序列的方式的技术。

产生原因:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

设置了transfer-encoding : chunked (http 2 将移除)后 , 请求主体按一系列块的形式发送,并且将省略Content-Length.在每一个块的开头需要用十六进制数说明当前块的长度,数值后接\r\n ,然后接块的内容,然后再接\r\n表示这个块结束。最后用长度0的块加\r\n\r\n表示终止块。
在这里插入图片描述

0x02 请求走私分类:

1.CL不为0 的get 请求
在这里插入图片描述

所以就可以借用admin 的cookie ,进行其他操作

2.CL-CL 走私:
在这里插入图片描述
在这里插入图片描述

3.CL-TE 走私
产生原因:
前端服务器不支持chunked encoding , 按照content length 来处理,后端则使用chunked encoding 来处理
实验地址: https://portswigger.net/web-security/request-smuggling/lab-basic-cl-te
payload: 发送两次
在这里插入图片描述

4.TE-CL 走私
在这里插入图片描述
实验地址:https://acd01f881f8f0a7c80e5860e009800b9.web-security-academy.net/
payload: 发送两次
在这里插入图片描述
5.TE-TE 走私
在这里插入图片描述
payload:
在这里插入图片描述

0x03 portswigger实验记录:

所有实验的地址: https://portswigger.net/web-security/all-labs
4. Lab: HTTP request smuggling, confirming a CL.TE vulnerability via differential responses

payload:
在这里插入图片描述

注意 :
(1) 必须要在GET 下面再添加额外的http 头 以及 添加的头后面不要再以\r\n 结尾,这样才会把第二次请求的POST / HTTP/1.1 变成一个头部的值拼接到走私数据中 , 否则会返回400错误
在这里插入图片描述
第二个请求拼接后应该像这样:
GET /404 HTTP/1.1
X-Ignore:xPOST /HTTP/1.1
HOST: …

5.HTTP request smuggling, confirming a TE.CL vulnerability via differential responses
在这里插入图片描述
原理: 把下一次的请求变成走私中POST 请求的数据部分

6.绕过前端服务器的安全控制CL-TE 🚩🚩
Exploiting HTTP request smuggling to bypass front-end security controls, CL.TE vulnerability
原理:
有时在一些网络环境中,前端服务器负责安全控制,只有被允许的请求才能杯转发给后端服务器,而后端服务器会无条件相信前端服务器转发过来的请求,对每个请求都进行响应。因此我们可以利用http 请求走私,将无法访问的请求走私给后端服务器并且获得响应。
首先发送这个payload 两次,可以看到成功绕过了前端的block ,访问到了/admin ,得到信息,只有用administrator 登录或者用localhost 来请求才能访问到admin 接口
在这里插入图片描述
所以再次构造payload:
在这里插入图片描述

但是问题来了,会出现duplicate header ,也就是host 头部重复,不用慌,可以构造一个post 数据包,使得第二次的请求变成post 的数据部分。
在这里插入图片描述
然后构造:
在这里插入图片描述
成功删除

对了,构造CL – TE 的时候有个技巧,就是打开content-length 更新,这样就不用去自己算长度了(默认就是打开的),但是构造TE-CL 的时候就必须关闭
在这里插入图片描述
7.绕过前端检验 TE-CL 🚩🚩
地址: https://portswigger.net/web-security/request-smuggling/exploiting/lab-bypass-front-end-controls-te-cl
同样:
在这里插入图片描述

然后:

在这里插入图片描述

然后:
在这里插入图片描述

总结:
构造TE-CL 中的content-length 一般为一个块长度的位数加2(\r\n),然后就开始写走私的部分,最后加上0\r\n\r\n
构造 CL-TE 先写0\r\n\r\n, 对于content-length 直接开启自动计算,然后写走私的部分
都是分块 : content-length ,0\r\n\r\nr ,以及走私的部分

8.获取前端服务器重写请求字段(也就是带出下一次请求的请求头和数据,只要走私的content-length长,就可以带出所有的头部信息和数据) 🚩🚩
地址: https://portswigger.net/web-security/request-smuggling/exploiting/lab-reveal-front-end-request-rewriting

在这里插入图片描述

首先要找输出点,发现使用search的时候,把我们search 的数据显示到了网页上,所以可以从search 入手

在这里插入图片描述

构造payload:

在这里插入图片描述

然后再次search ,发现显示出来了前端服务器增加的请求头(当然可以直接上面的payload 重放两次)

在这里插入图片描述

然后发送两次,成功解决:

在这里插入图片描述

9.获取其他用户的请求 🚩🚩🚩
地址 :https://portswigger.net/web-security/request-smuggling/exploiting/lab-capture-other-users-requests
这有一个小问题,首先验证是否存在走私:
在这里插入图片描述

第二次发送后返回的是404 ,而不是400
然后修改请求为GPOST ,发现也是404,
在这里插入图片描述

所以这里存在走私,只不过返回的响应码是404,而不是400
然后找到评论处发现可以带出数据:
构造payload:

在这里插入图片描述

这里又有个问题,就是虽然说了会自动有用户来make their own request ,但是好像事实并没有,所以这里发送了两次抓到自己的数据,但如果我们第一次发送了payload 后,接着又有用户发送请求,这个时候就可以带出其他用户的cookie 了。

在这里插入图片描述
在这里插入图片描述

10.制作反射xss
地址: https://portswigger.net/web-security/request-smuggling/exploiting/lab-deliver-reflected-xss
首先题目以及提示我们,User-Agent存在反射型xss:
在这里插入图片描述

那我们构造:
在这里插入图片描述

然后浏览器访问(可以不是/post?postid=2,只要是这个网址,由于http request smuggle都会重定向到/post?postid=2 这里),弹窗成功
在这里插入图片描述

11.把on-site 重定向到住转化为任意重定义(原句是 turn an on-site redirect into an open redirect)
比如,apache 和 iis 服务器 如果接收到一个文件夹请求但是没有斜杠闭合,那么会取host 然后重定向到host 加这个文件夹名 再加斜杠:

在这里插入图片描述

本来是无害的,但是如果利用请求走私,就可以重定向到恶意网站上去,变成一个open redirect
大致意思就是发送:

在这里插入图片描述

然后就会重定向到:
HTTP/1.1 301 Moved Permanently
Location: https://www.evil.com/home/

危害: 就可以让www.evil.com/home/ 返回恶意资源,如果原来请求的是一个js 文件,那么就可以返回一个恶意的js

12.缓存投毒: (感觉环境有问题,没有复现成功)
一些前端服务器为了性能往往会对静态文件进行缓存,如果存在http 请求走私漏洞,则可以利用走私漏洞来缓存投毒
地址: https://portswigger.net/web-security/request-smuggling/exploiting/lab-perform-web-cache-poisoning
说明:
如果想要构造自己恶意的js 文件,需要结合上面的重定向。
危害还比较大,造成缓存投毒后,后面的用户都会中毒。
首先在平台中exploit 的服务器上构造/pos文件 内容为alert(1)
然后构造payload:
在这里插入图片描述
在这里插入图片描述

以此发送第一个包和第二个包,反复多次,最后就会使得labHeader.js 缓存的内容为alert(1)

思考: 由于host 字段的限制,感觉要么就是利用目标服务器的其他资源来污染,比如使得1.js 缓存的内容变为 2.js
要么就是配合上面的重定向来

13,缓存欺骗: (比较鸡肋)
首先登录carlos 账号:

在这里插入图片描述
目标是获得carlos 的api key:

在这里插入图片描述

然后构造payload:
在这里插入图片描述

然后去访问一个公共的静态资源,比如:
https://ace01f751f186159806d37ce0058008b.web-security-academy.net/resources/images/logoAcademy.svg

在这里插入图片描述
原理是:

在这里插入图片描述

但是感觉有点鸡肋,因为还是需要携带cookie (B用户的cookie ),所以不能通过 A 用户得到 B 用户的敏感信息,因为A 用户请求的时候携带的是自己的cookie ,所以获得的也是自己的信息,也就是只能自己欺骗自己

0x04 如何检测http requests smuggle漏洞

1.使用计时计数查找http 请求走私
(1) 检测CL.TE 漏洞:
在这里插入图片描述
之前还在疑问为什么这个利用延时检测技术的payload 可以检测CL.TE 呢,感觉如果是TE.CL ,前端服务器处理时也会延迟等待下一个块,还觉得可能是pipeline 的原因,前端和客户端没有使用pipeline 。原来是因为这个’Q’ 是一个无效块,所以前端直接拒绝了

(2)检测TE.CL 漏洞:
在这里插入图片描述

2.使用差异响应码
这个其实就是上面实验中利用请求走私来得到404是一样的,但是首先通过上面的时间延时来检测后 ,最好再用差异响应码来判断
在这里插入图片描述
一个问题就是在活跃的站点,可能需要尝试千次,因为很有可能中毒的响应被发送到其他用户那里而自己却没有收到

3.挖掘时的注意事项:
在这里插入图片描述
第一点没搞明白,为啥需要不同的连接来验证呢?同一个连接不也可以验证吗,难道是实际场景中,要影响其他用户才能算是漏洞吗?这点如果有大佬知道的话,可以一起讨论一下

0x06 检测脚本

其实burpsuit 有相应的插件 ,但是我自己安装之后不怎么好使,网上找到了一个python3 写的检测脚本,挺好用的:

#!/usr/bin/python3
'''
Author: xph
CreateTime: 2019-09-18
'''

from requests import Request, Session
from requests.exceptions import ReadTimeout
import urllib3
import requests
import collections
import http.client

http.client._is_legal_header_name = lambda x: True
http.client._is_illegal_header_value = lambda x: False
urllib3.disable_warnings()

fp = open("res.txt", 'a')
fp.write("\n" + "-" * 50 + "\n")
fp.flush()


class HTTP_REQUEST_SMUGGLER():

    def __init__(self, url):
        self.headers_payload = []
        self.valid = False
        self.type = ""
        self.url = url
        self.Transfer_Encoding1 = [["Transfer-Encoding", "chunked"],
                                   ["Transfer-Encoding ", "chunked"],
                                   ["Transfer_Encoding", "chunked"],
                                   ["Transfer Encoding", "chunked"],
                                   [" Transfer-Encoding", "chunked"],
                                   ["Transfer-Encoding", "  chunked"],
                                   ["Transfer-Encoding", "chunked"],
                                   ["Transfer-Encoding", "\tchunked"],
                                   ["Transfer-Encoding", "\u000Bchunked"],
                                   ["Content-Encoding", " chunked"],
                                   ["Transfer-Encoding", "\n chunked"],
                                   ["Transfer-Encoding\n ", " chunked"],
                                   ["Transfer-Encoding", " \"chunked\""],
                                   ["Transfer-Encoding", " 'chunked'"],
                                   ["Transfer-Encoding", " \n\u000Bchunked"],
                                   ["Transfer-Encoding", " \n\tchunked"],
                                   ["Transfer-Encoding", " chunked, cow"],
                                   ["Transfer-Encoding", " cow, "],
                                   ["Transfer-Encoding", " chunked\r\nTransfer-encoding: cow"],
                                   ["Transfer-Encoding", " chunk"],
                                   ["Transfer-Encoding", " cHuNkeD"],
                                   ["TrAnSFer-EnCODinG", " cHuNkeD"],
                                   ["Transfer-Encoding", " CHUNKED"],
                                   ["TRANSFER-ENCODING", " CHUNKED"],
                                   ["Transfer-Encoding", " chunked\r"],
                                   ["Transfer-Encoding", " chunked\t"],
                                   ["Transfer-Encoding", " cow\r\nTransfer-Encoding: chunked"],
                                   ["Transfer-Encoding", " cow\r\nTransfer-Encoding: chunked"],
                                   ["Transfer\r-Encoding", " chunked"],
                                   ["barn\n\nTransfer-Encoding", " chunked"],
                                   ]

        self.Transfer_Encoding = list(self.Transfer_Encoding1)

        for x in self.Transfer_Encoding1:
            if " " == x[1][0]:
                for i in [9, 11, 12, 13]:
                    # print (type(chr(i)))
                    c = str(chr(i))
                    self.Transfer_Encoding.append([x[0], c + x[1][1:]])

        self.payload_headers = []
        self.n1 = 1
        for x in self.Transfer_Encoding:
            headers = collections.OrderedDict()
            headers[x[0]] = x[1]
            headers['Cache-Control'] = "no-cache"
            headers['Content-Type'] = "application/x-www-form-urlencoded"
            headers['User-Agent'] = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)"
            self.payload_headers.append(headers)
            self.n1 = self.n1 + 1


    def detect_CLTE(self, headers={}, payload=""):
        s = Session()
        req = Request('POST', self.url, data=payload)
        prepped = req.prepare()
        prepped.headers = headers
        resp_time = 0
        try:
            resp = s.send(prepped, verify=False, timeout=10)
            resp_time = resp.elapsed.total_seconds()
            return resp_time
        except Exception as e:
            print (e)
            resp_time = 10
            if isinstance(e, ReadTimeout):
                print("requests.exceptions.ReadTimeout")
                return resp_time

    def detect_TECL(self, headers={}, payload=""):
        s = Session()
        req = Request('POST', self.url, data=payload)
        prepped = req.prepare()
        prepped.headers = headers
        resp_time = 0
        try:
            resp = s.send(prepped, verify=False, timeout=10)
            resp_time = resp.elapsed.total_seconds()
            print(resp, resp_time)
        except Exception as e:
            print (e)
            if isinstance(e, ReadTimeout):
                resp_time = 10
                print("requests.exceptions.ReadTimeout")

        # print(resp_time)
        return resp_time

    def check_CLTE(self):
        n = 0
        payloads = self.payload_headers if self.headers_payload == [] else self.headers_payload
        for headers in payloads:
            n = n + 1
            headers['Content-Length'] = 4
            payload = "1\r\nZ\r\nQ\r\n\r\n\r\n"
            print(self.url, headers)
            t2 = self.detect_CLTE(headers, payload)
            if t2 == None: t2 = 0
            if t2 < 5:
                continue

            headers['Content-Length'] = 11
            print(self.url, headers)
            payload = "1\r\nZ\r\nQ\r\n\r\n\r\n"
            t1 = self.detect_CLTE(headers, payload)

            if t1 == None: t1 = 1


            print (t1, t2)
            if t2 > 5 and t2 / t1 >= 5:
                self.valid = True
                self.type = "CL-TE"
                self.headers_payload = [headers]
                return True
        return False

    def check_TECL(self):
        n = 0
        payloads = self.payload_headers if self.headers_payload == [] else self.headers_payload
        for headers in payloads:
            n = n + 1
            payload = "0\r\n\r\nX"
            headers['Content-Length'] = 6
            print(self.url, headers)
            t2 = self.detect_TECL(headers, payload)
            if t2 == None: t2 = 0
            if t2 < 5:
                continue

            print(self.url, headers)
            payload = "0\r\n\r\n"
            headers['Content-Length'] = 5
            t1 = self.detect_TECL(headers, payload)

            if t1 == None: t1 = 1
            if t2 == None: t2 = 0
            # print (t1, t2)

            if t2 > 5 and t2 / t1 >= 5:
                self.valid = True
                self.type = "TE-CL"
                self.headers_payload = [headers]
                return True
        return False
    def run(self):
        try:
            h = {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"}
            requests.get(self.url, headers=h, verify=False, timeout=10)
            if not self.check_CLTE():
                self.check_TECL()
        except Exception as e:
            print(e)
            print("timeout: " + self.url)
        return self.recheck()

    def recheck(self):
        print("recheck")
        print(self.valid, self.type)
        if self.valid:
            if self.type == "CL-TE":
                if self.check_CLTE():
                    print ("Find CL-TE: " + self.url)
                    payload_key = list(self.headers_payload[0])[0]
                    payload_value = self.headers_payload[0][payload_key]
                    payload = str([payload_key, payload_value])
                    print(payload)
                    fp.write("CL-TE\t poc:" + payload + "\t" + self.url + "\n")
                    fp.flush()
                    return ["CL-TE", payload]
            else:
                if self.check_TECL():
                    print ("Find TE-CL: " + self.url)
                    payload_key = list(self.headers_payload[0])[0]
                    payload_value = self.headers_payload[0][payload_key]
                    payload = str([payload_key, payload_value])
                    print(payload)
                    fp.write("TE-CL\t poc:" + payload + "\t" + self.url + "\n")
                    fp.flush()
                    return ["TE-Cl", payload]

def func(url):
    a = HTTP_REQUEST_SMUGGLER(url)
    print(a.run())

def main():
    import threadpool
    iter_list = open("urls.txt").read().split("\n")

    pool = threadpool.ThreadPool(30)
    thread_requests = threadpool.makeRequests(func, iter_list)
    [pool.putRequest(req) for req in thread_requests]
    pool.wait()

func("https://ac1e1fd61fb164208167481400b20044.web-security-academy.net/")

参考:
1.https://paper.seebug.org/1048
2.https://portswigger.net/web-security/request-smuggling/exploiting
3.https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn
4.https://blog.riskivy.com/%E6%B5%81%E9%87%8F%E5%A4%B9%E5%B8%A6http-request-smuggling-%E6%A3%80%E6%B5%8B%E6%96%B9%E6%A1%88%E7%9A%84%E5%AE%9E%E7%8E%B0/ HRS python 检测脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值