简介

Mitmproxy

mitmproxy 可为HTTP和WebSocket提供交互式的拦截代理,可以通过自定义python脚本进行二次开发,对请求/响应数据包内容进行操作。

实战价值

基于js的前端加密/加签Web站点,利用mitmproxy自定义脚本并联动burp,实现自动化加解密/签名操作,提升测试效率,示例:

【原始数据包】

image-20250609133339197

【启用Mitmproxy后数据包】

image-20250609133413976

安装使用

安装

官网地址:https://www.mitmproxy.org/

image-20250609133515734

命令行启动

  • mitmproxy.exe 命令行运行
安装路径/bin/mitmproxy.exe -p 5050
  • 常用的参数
-p    指定端口
-s    指定需要加载的python脚本
-m    指定模式[regular,transparent,socks5,reverse:SPEC,upstream:SPEC]
-k    https场景下忽略证书错误

image-20250609133637154

证书安装

成功运行后,启用代理访问以下链接安装证书:

http://mitm.it/

image-20250609133823917

若未成功启用代理,将出现如下报错:

image-20250609133843708

成功安装证书后,就可以抓取https的流量了

image-20250609133919531

脚本编写

上游代理替换请求头、请求体python脚本示例:

(下面代码替换请求包中的请求头和请求体)

import mitmproxy.http

class Modify:
def request(self, flow: mitmproxy.http.HTTPFlow):
# 请求体
url = str(flow.request.url)
if 'www.baidu.com' in url:
#替换请求头
flow.request.headers["headertest"] = "headertest"
#替换请求体
flow.request.urlencoded_form = [("bodytest","bodytest")]

addons = [
Modify()
]

运行mitmproxy,监听8081端口,指定脚本

.\mitmproxy.exe  -p 8081 -s ..\pydemo.py --quiet

image-20250609134409981

burp启用上游代理,指定域名与端口

image-20250609134435342

burp发送请求:

image-20250609134510253

Mitmproxy处理请求

image-20250609134545342

tips:默认安装的mitmproxy运行自带的python环境,若报错缺少依赖,将对应依赖添加至 /bin 目录下即可

image-20250609134620334

实战案例

自动化加签sign

该应用系统对请求体进行签名,服务端验签不通过时响应——“请勿篡改请求参数,非法请求系统资源”

image-20250609134717493

利用上游代理,对burp传入的请求包进行签名操作,整体数据流向图如下:

image-20250609134751356

1、企业Mitmproxy作为上游代理,指定Python脚本,监听8081端口

.\mitmproxy.exe -p 8081 -s ..\signDemo.py --quiet

image-20250609134847666

signDemo.py脚本示例:

import mitmproxy.http
import base64
from Cryptodome.Cipher import AES

def sign(data):
key = 'xxxxxxx'
iv = 'xxxxxxx'
#zeropadding,支持中文utf8编码需计算s.encode()
pad = lambda s: s + (16 - len(s.encode())%16) * chr(0)
data = pad(data)
# 字符串补位
cipher = AES.new(key.encode('utf8'), AES.MODE_CBC, iv.encode('utf8'))
encryptedbytes = cipher.encrypt(data.encode('utf8'))
# 加密后得到的是bytes类型的数据
encodestrs = base64.b64encode(encryptedbytes)
# 使用Base64进行编码,返回byte字符串
enctext = encodestrs.decode('utf8')
# 对byte字符串按utf-8进行解码
if len(enctext) > 30:
signstr = enctext[-30:]
return(signstr)
else:
return(enctext)

class Modify:
def request(self, flow: mitmproxy.http.HTTPFlow):
# 请求体
body = str(flow.request.text)
url = str(flow.request.url)
if 'xxxxxxxx' in url:
#替换请求头
flow.request.headers["sign"] = sign(body)

addons = [
Modify()
]

2、启用mitmproxy作为上游代理,指定python脚本,监听8081端口

image-20250609135035189

此时可利用Repeater模块,直接修改请求包

image-20250609135107905

观察mitmproxy日志可见,sign请求头已完成签名(由上图中的 “testtesttesttest” 自动替换为签名参数)

image-20250609135132111

自动化加解密

该应用系统请求/响应加密

image-20250609135424972

利用一级代理加上游代理,实现业务功能正常使用的同时,burp侧数据明文化,整体数据流向图如下:

tips: 为保证业务系统自有加解密逻辑正常,需保障 传到服务端的请求、传到浏览器的响应 ,均为密文状态。

image-20250609135514021

1、启用Mitmproxy一级代理

.\mitmproxy.exe -p 8282 -m upstream:http://127.0.0.1:2233 -k -s ..\crypt\decrypt.py

decrypt.py脚本示例:


import mitmproxy.http
import mitmproxy.ctx
import base64
from Cryptodome.Cipher import DES3


def decrypt(enctext):
key = 'xxxxxxxxx'
iv = 'xxxxxxxxx'
# 使用Base64进行解码,将字符串转为bytes类型
decodestr = base64.b64decode(enctext.encode('utf8'))
# 创建一个3DES解密对象
cipher = DES3.new(key.encode('utf8'), DES3.MODE_CBC, iv.encode('utf8'))
# 解密数据
decryptedbytes = cipher.decrypt(decodestr)
# 去除填充
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
dectext = unpad(decryptedbytes.decode('utf8'))
# print(dectext)
return dectext

def encrypt(data):
key = 'xxxxxxxxx'
iv = 'xxxxxxxxx'
#pkcs5
pad = lambda data: data + (8 - len(data.encode('utf-8')) % 8) * chr(8 - len(data.encode('utf-8')) % 8)
data = pad(data)
# 字符串补位
cipher = DES3.new(key.encode('utf8'), DES3.MODE_CBC, iv.encode('utf8'))
encryptedbytes = cipher.encrypt(data.encode('utf8'))
# 加密后得到的是bytes类型的数据
encodestrs = base64.b64encode(encryptedbytes)
# 使用Base64进行编码,返回byte字符串
enctext = encodestrs.decode('utf8')
# 对byte字符串按utf-8进行解码
# print(enctext)
return enctext

class dataDecrypt:

def request(self, flow: mitmproxy.http.HTTPFlow):
#请求解密
url = str(flow.request.url)
if flow.request.url.startswith("xxxxxxxxx"):
bodydata = str(flow.request.urlencoded_form["param"])
#替换
flow.request.urlencoded_form["param"] = decrypt(bodydata)


def response(self, flow: mitmproxy.http.HTTPFlow):
#响应加密
response_data = flow.response
if flow.request.url.startswith("xxxxxxxxx"):
respData = str(response_data.text)
bodydata = encrypt(respData)
#替换
flow.response.text = str(bodydata).replace("\n","").replace("\\n","")


addons = [
dataDecrypt()
]

2、burp监听22333端口

image-20250609135842867

3、启用Mitmproxy作为上游代理

.\mitmproxy.exe -p 8081 -s ..\crypt\encrypt.py --quiet

image-20250609135941506

encrypt.py脚本示例:


import mitmproxy.http
import mitmproxy.ctx
import base64
from Cryptodome.Cipher import DES3
import json


def decrypt(enctext):
key = 'xxxxxxxxx'
iv = 'xxxxxxxxx'
# 使用Base64进行解码,将字符串转为bytes类型
decodestr = base64.b64decode(enctext.encode('utf8'))
# 创建一个3DES解密对象
cipher = DES3.new(key.encode('utf8'), DES3.MODE_CBC, iv.encode('utf8'))
# 解密数据
decryptedbytes = cipher.decrypt(decodestr)
# 去除填充
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
dectext = unpad(decryptedbytes.decode('utf8'))
# print(dectext)
return dectext

def encrypt(data):
key = 'xxxxxxxxx'
iv = 'xxxxxxxxx'
#pkcs5
pad = lambda data: data + (8 - len(data.encode('utf-8')) % 8) * chr(8 - len(data.encode('utf-8')) % 8)
data = pad(data)
# 字符串补位
cipher = DES3.new(key.encode('utf8'), DES3.MODE_CBC, iv.encode('utf8'))
encryptedbytes = cipher.encrypt(data.encode('utf8'))
# 加密后得到的是bytes类型的数据
encodestrs = base64.b64encode(encryptedbytes)
# 使用Base64进行编码,返回byte字符串
enctext = encodestrs.decode('utf8')
# 对byte字符串按utf-8进行解码
# print(enctext)
return enctext

class dataEncrypt:

def request(self, flow: mitmproxy.http.HTTPFlow):
#请求加密
if flow.request.url.startswith("xxxxxxxxx"):
bodydata = str(flow.request.urlencoded_form["param"])
#替换
flow.request.urlencoded_form["param"] = encrypt(bodydata)

def response(self, flow: mitmproxy.http.HTTPFlow):
response_data = flow.response
if flow.request.url.startswith("xxxxxxxxx"):
respData = str(response_data.text)
bodydata = decrypt(respData)
#替换
flow.response.text = str(bodydata)


addons = [
dataEncrypt()
]

4、burp配置上游代理

image-20250609140027707

此时抓包burp侧数据包均为明文展示

image-20250609140052649

image-20250609140116449

且前端页面可以正常使用

image-20250609140142885

观察mitmproxy可见,请求角度

  • 一级代理脚本将密文处理为明文上送至burp:

image-20250609140241655

  • 二级代理将明文加密为密文后上送至服务端

image-20250609140310423

响应角度:

  • 二级代理将密文处理为明文,下发至burp

image-20250609140352405

  • 一级代理将明文加密为密文,下发至浏览器

image-20250609140412947

附录

Python脚本中常用API如下:

[request]:

flow.request.headers #获取所有头信息,包含Host、User-Agent、Content-type等字段
flow.request.headers['nonce'] #获取请求头中的nonce字段值
flow.request.url #完整的请求地址,包含域名及请求参数,但是不包含放在body里面的请求参数
flow.request.pretty_url #同flow.request.url目前没看出什么差别
flow.request.host #域名
flow.request.method #请求方式。POST、GET等
flow.request.scheme #什么请求 ,如https
flow.request.path # 请求的路径,url除域名之外的内容
flow.request.headers.pop('signature') #删除请求头signature
flow.request.headers.add('signature') #添加请求头signature

flow.request.text #获取请求体内容
flow.request.get_text() #请求中body内容,有一些http会把请求参数放在body里面,那么可通过此方法获取,返回字典类型
flow.request.query #返回MultiDictView类型的数据,url直接带的键值参数
flow.request.get_content()#bytes,结果如flow.request.get_text()
flow.request.raw_content #bytes,结果如flow.request.get_content()
flow.request.urlencoded_form #MultiDictView,content-type:application/x-www-form-urlencoded时的请求参数,不包含url直接带的键值参数
flow.request.multipart_form #MultiDictView,content-type:multipart/form-data时的请求参数,不包含url直接带的键值参数



[response]:
flow.response.status_code #状态码
flow.response.text#返回内容,已解码
flow.response.content #返回内容,二进制
flow.response.setText()#修改返回内容,不需要转码

参考

https://mp.weixin.qq.com/s/tYTya7R2bT6Rc-nN3O1Dng?poc_token=HC50RmijdfyS6d5MB4g5m6wHAydx4mf2CPHAaTbj

https://blog.csdn.net/qianwenjun_19930314/article/details/88227335

https://www.freebuf.com/articles/web/243370.html

https://www.t00ls.com/viewthread.php?tid=68717&highlight=mitmproxy