简介
Mitmproxy
mitmproxy 可为HTTP和WebSocket提供交互式的拦截代理,可以通过自定义python脚本进行二次开发,对请求/响应数据包内容进行操作。
实战价值
基于js的前端加密/加签Web站点,利用mitmproxy自定义脚本并联动burp,实现自动化加解密/签名操作,提升测试效率,示例:
【原始数据包】
【启用Mitmproxy后数据包】
安装使用
安装
官网地址:https://www.mitmproxy.org/
命令行启动
安装路径/bin/mitmproxy.exe -p 5050
|
-p 指定端口 -s 指定需要加载的python脚本 -m 指定模式[regular,transparent,socks5,reverse:SPEC,upstream:SPEC] -k https场景下忽略证书错误
|
证书安装
成功运行后,启用代理访问以下链接安装证书:
http://mitm.it/
若未成功启用代理,将出现如下报错:
成功安装证书后,就可以抓取https的流量了
脚本编写
上游代理替换请求头、请求体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
|
burp启用上游代理,指定域名与端口
burp发送请求:
Mitmproxy处理请求
tips:默认安装的mitmproxy运行自带的python环境,若报错缺少依赖,将对应依赖添加至 /bin 目录下即可
实战案例
自动化加签sign
该应用系统对请求体进行签名,服务端验签不通过时响应——“请勿篡改请求参数,非法请求系统资源”
利用上游代理,对burp传入的请求包进行签名操作,整体数据流向图如下:
1、企业Mitmproxy作为上游代理,指定Python脚本,监听8081端口
.\mitmproxy.exe -p 8081 -s ..\signDemo.py --quiet
|
signDemo.py脚本示例:
import mitmproxy.http import base64 from Cryptodome.Cipher import AES
def sign(data): key = 'xxxxxxx' iv = 'xxxxxxx' 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')) encodestrs = base64.b64encode(encryptedbytes) enctext = encodestrs.decode('utf8') 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端口
此时可利用Repeater模块,直接修改请求包
观察mitmproxy日志可见,sign请求头已完成签名(由上图中的 “testtesttesttest” 自动替换为签名参数)
自动化加解密
该应用系统请求/响应加密
利用一级代理加上游代理,实现业务功能正常使用的同时,burp侧数据明文化,整体数据流向图如下:
tips: 为保证业务系统自有加解密逻辑正常,需保障 传到服务端的请求、传到浏览器的响应 ,均为密文状态。
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' decodestr = base64.b64decode(enctext.encode('utf8')) 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')) return dectext
def encrypt(data): key = 'xxxxxxxxx' iv = 'xxxxxxxxx' 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')) encodestrs = base64.b64encode(encryptedbytes) enctext = encodestrs.decode('utf8') 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端口
3、启用Mitmproxy作为上游代理
.\mitmproxy.exe -p 8081 -s ..\crypt\encrypt.py --quiet
|
encrypt.py脚本示例:
import mitmproxy.http import mitmproxy.ctx import base64 from Cryptodome.Cipher import DES3 import json
def decrypt(enctext): key = 'xxxxxxxxx' iv = 'xxxxxxxxx' decodestr = base64.b64decode(enctext.encode('utf8')) 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')) return dectext
def encrypt(data): key = 'xxxxxxxxx' iv = 'xxxxxxxxx' 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')) encodestrs = base64.b64encode(encryptedbytes) enctext = encodestrs.decode('utf8') 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配置上游代理
此时抓包burp侧数据包均为明文展示
且前端页面可以正常使用
观察mitmproxy可见,请求角度:
响应角度:
附录
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