Mitmproxy 数据包解密实战篇

2024-06-23 285 0

1 前言

mitmproxy 是一组工具,提供用于 HTTP/1、HTTP/2 和 WebSocket 的交互式、支持 SSL/TLS 的拦截代理。[1]。

一般提到的mitmproxy指代了mitmproxy、mitmweb和mitmdump,尽管它们提供的功能相同,但接口不同,分别适用于不同的使用场景。

名称 描述
mitmproxy 一个交互式的、支持SSL/TLS的拦截代理,具有用于HTTP/1、HTTP/2和WebSockets的控制台界面。
mitmweb mitmproxy 的基于网页的界面。
mitmdump mitmproxy 的命令行版本。可以将其理解为HTTP的tcpdump。

对于安全研究人员来说,mitmproxy可以用于拦截加密数据包,然后通过解密数据并代理到BurpSuite,得到的明文数据包更方便进行安全研究和渗透测试。

2 安装

macOS安装mitmproxy如下,其他系统可以从官网下载[2]

brew install mitmproxy

3 基本使用

上文介绍过mitmproxy、mitmweb、mitmdump三者的区别,对于想要持久化修改流量的场景mitmdump更适合,下面将用mitmdump做演示。

使用mitmdump的2个步骤如下:

3.1 编写插件

官方提供了部分插件编写的例子[3],例如http-add-header.py:

如下代码主要实现了在每个response中添加HTTP头。

class AddHeader:
    def __init__(self):
        self.num = 0

    def response(self, flow):
        self.num = self.num + 1
        flow.response.headers["count"] = str(self.num)

addons = [AddHeader()]

3.2 命令行启动

mitmdump -s http-add-header.py -p 6666

此时将浏览器代理设置为6666端口,如图1,就可以实现正常的改包了。

Mitmproxy 数据包解密实战篇插图

图1 配置浏览器代理

4 应用实战

某应用接口数据加密(如图2所示),这导致不太方便对数据包内容进行直接判断,这种情况可以通过mitmproxy实现查看和编辑明文数据。

Mitmproxy 数据包解密实战篇插图1

图2 应用数据包

4.1 应用加密/解密分析

在应用程序的jar包中查找对应的加密类,类中方法的部分代码如下:

public static String[] decode(String data) {
      byte[] dataByte = CodeHandler.strToByteArrayByUTF8(data);
      dataByte = CodeHandler.deCode(dataByte);
      String decodedData = CodeHandler.byteArrayToStrByUTF8(dataByte);
      ......

从上述代码可以看出,代码首先将str转换为byte,然后使用解密函数进行解密,最后再将byte转换回str。

继续跟进到CodeHandler.deCode,其代码如下:

public byte[] decode(byte[] dataByte) {
    int i = 0;

    for(int j = 0; j < dataByte.length; ++j) {
        byte tmp = dataByte[i];
        if (tmp > 0 && tmp < decodeArray.length) {
            byte encodeChar = decodeArray[tmp];
            if (encodeChar != 0) {
                dataByte[i] = encodeChar;
            }
        }

        ++i;
    }

    return dataByte;
}

所以根据java代码写出的python加密解密脚本如下:

import logging
import re

class Mimit():
    decode_array = [
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        32, 87, 0, 0, 0, 47, 0, 56, 97, 89, 84, 43, 0, 103, 106, 37, 113, 49, 121, 78, 114, 112, 110, 48,
        76, 55, 123, 0, 0, 0, 0, 0, 0, 40, 88, 120, 115, 41, 77, 107, 71, 104, 53, 52, 80, 54, 51, 65,
        33, 117, 105, 108, 68, 90, 66, 83, 122, 81, 86, 93, 0, 91, 0, 102, 0, 69, 119, 73, 109, 126, 45,
        118, 100, 99, 82, 116, 75, 57, 39, 79, 101, 46, 72, 42, 67, 50, 74, 111, 70, 95, 85, 58, 0, 0, 98, 0
    ]

    def request(self, flow):
        if flow.request.path.endswith("/RMIServlet") and flow.request.method == "POST":
            req = flow.request.get_text()
            logging.info("requestData:" + req)
            match = re.search(r'encode=([^&]+)', req)
            decoded_str = ""
            if match:
                encode_value = match.group(1)
                data_byte = bytearray(encode_value, "UTF-8")
                decoded_byte = self.decode(data_byte)
                decoded_str = decoded_byte.decode("UTF-8")
                logging.info(decoded_str)
            else:
                logging.info("Encode value not found")
            array = decoded_str.split("+")
            data = f'className={array[0]}&methodName={array[1]}&ampparams={array[2]}'
            flow.request.set_text(data)

    def response(self, flow):
        if flow.request.path.endswith("/RMIServlet") and flow.request.method == "POST":
            response = flow.response.get_text()
            data_byte = bytearray(response, "UTF-8")
            encoded_byte = self.encode(data_byte)
            encoded_str = encoded_byte.decode("UTF-8")
            flow.response.set_text(encoded_str)

    def decode(self, data_byte):

        for i in range(len(data_byte)):
            tmp = data_byte[i]
            if 0 < tmp < len(self.decode_array):
                encode_char = self.decode_array[tmp]
                if encode_char != 0:
                    data_byte[i] = encode_char
        return data_byte

    def encode(self, data_byte):
        for i in range(len(data_byte)):
            tmp = data_byte[i]
            if 0 < tmp < len(self.decode_array) and tmp not in (37, 47):
                encode_char = self.decode_array[tmp]
                if encode_char != 0:
                    data_byte[i] = encode_char
        return data_byte


addons = [Mimit(), ]

4.2 代理配置

使用--mode upstream:来设置上游代理联动BurpSuite,加上--ssl-insecure忽略 SSL 证书验证:

mitmdump -p 6666 -s main.py  --mode upstream:http://127.0.0.1:6662 --ssl-insecure

浏览器插件配置和图1同理,BurpSuite配置Proxy listeners,如图3所示。

Mitmproxy 数据包解密实战篇插图2

图3 BurpSuite配置

浏览器发送数据包的顺序如图4所示。

Mitmproxy 数据包解密实战篇插图3

图4 数据包流程图

有人可能会对流程图感到困惑:为何要在解密后将数据传递给BurpSuite和服务器,再对返回结果进行加密后再提供给浏览器?这是因为该程序的服务器端能够识别类似于className=xxx&methodName=xxx的明文传递,但服务器返回的结果也是明文,如图5。如果结果是明文,该程序前端JavaScript代码将无法识别返回值,导致网页加载失败。因此,需要对服务器返回的明文数据进行加密处理。

Mitmproxy 数据包解密实战篇插图4

图5 明文传递数据包

所以当根据上文配置好后,便可以成功拦截到明文数据包了,如图6。

Mitmproxy 数据包解密实战篇插图5

图6 明文数据包

5 其他方式

在某些情况下,我们需要的数据包流程图如图7,这种情况是在某些服务器只支持识别密文时,让我们也能够在BurpSuite上捕获并编辑明文数据,再将其加密后发送给服务器。

Mitmproxy 数据包解密实战篇插图6

图7 流程图

这种情况配置内容应该如下:

1、首先启动第一个mitmdump,定义好自己的解密脚本并将解密后的内容传递给BurpSuite。

mitmdump -p 6666 -s dec.py  --mode upstream:http://127.0.0.1:6662 --ssl-insecure

2、再启动第二个mitmdump,定义好加密脚本。

mitmdump -p 7777 -s enc.py

同时配置好BurpSuite的UpStream proxy servers,如图8。

Mitmproxy 数据包解密实战篇插图7

图8 BurpSuite配置UpStream proxy servers

这样就能将从BurpSuite出来的流量再次进行加密了。

6 参考链接

https://mitmproxy.org/

https://mitmproxy.org/downloads/

https://docs.mitmproxy.org/stable/addons-examples/

https://xz.aliyun.com/t/13218


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

HTB-Infiltrator:一文带你走进域渗透
JAVA安全 | Classloader:理解与利用一篇就够了
多角度揭秘威胁行为组织CryptoCore复杂的加密货币欺诈活动
网络空间的“边水往事”?针对华语黑产及用户进行攻击的 APT-K-UN3 活动分析
伪装“黑神话悟空修改器”传播木马的活动分析
靶场战神为何会陨落?

发布评论