我们经常了解到的抓包工具有wireshark、fiddler、charles等,mitmproxy也是一个代理工具,突出的优点是可以命令行方式或脚本的方式进行代理,可以对请求数据进行二次开发(二次定制)
官网:https://mitmproxy.org/
官方文档:https://docs.mitmproxy.org/stable/
本章的学习是参考了这篇优秀教程:使用 mitmproxy + python 做拦截代理
本文侧重点是二次定制内容
mitmproxy是基于python环境进行二次定制的,我们需要先配置python环境,python环境的配置可以网上找教程,这里不细述了。
注意:最好是配置python3.8及以上的python环境,如果是以下的版本,则最好安装5.2.0版本的mitmproxy,因为其他的版本有编码问题
配置好python环境,通过pip安装mitmproxy
pip install mitmproxy
通过mitmdump --version
可以查看mitmproxy是否安装成功,安装成功的话,命令打印内容如下:
Mitmproxy: 9.0.1
Python: 3.10.7
OpenSSL: OpenSSL 3.0.7 1 Nov 2022
Platform: Windows-10-10.0.19044-SP0
现在很多请求都是https的,https的抓包需要安装证书。第一次启动mitmproxy时,就会在当前用户目录下生成.mitmproxy
目录,目录存放了证书相关信息,官网对于证书的描述如下:
启动时是用的Administrator用户,所以我是在Administrator目录下找到.mitmproxy目录:
根据官网的描述,windows用的是mitmproxy-ca-cert.p12这个,双击完成证书安装
在这里博主遇到了一个坑,就是证书已经安装了,但是访问https还是报了有风险,可以通过netstat -ano | findstr "端口号"
查看端口被占用情况,如果有多个应用占用,我们就重启mitmproxy,使用Ctrl+c停止当前的mitmproxy,然后使用mitmdump -p 新端口
命令占用其他端口,再去抓包https就不会有报错了。
Android端的大部分请求都是https,如果想抓包Android的请求,需要:
我们可以通过mitmproxy
、mitmdump
、mitmweb
中任意一个命令即可启动mitmproxy,这3个命令的功能是一致的,只是交互界面不太一样
如下是mitmproxy
的交互界面:类似于命令行界面,实时展示抓包到的请求,能通过命令行窗口底下的提示进行请求过滤、数据查看等
mitmdump
类似于linux的tail,只能默默看着数据一溜烟的更新,还不能过滤
mitmweb
命令启动后,除了命令行会像mitmdump
一样打印抓包到的请求外,还会自动打开一个web页面,显示抓包到的请求,在web页面,可以对请求进行过滤、查看等
mitmweb
命令启动后,前2行会打印监听端口web服务端口,如上,mitmweb
执行后,第一行说明mitmproxy监听了8080端口,第二行说明了可以通过 http://127.0.0.1:8081/ 打开web页面(会自动打开)
在web界面,Search是对请求数据进行过滤;Intercept对匹配的请求进行暂停操作(可),类似于fiddler或charles的断点。
我们将age编辑为233,然后点击Done按钮退出编辑状态,点击左上角的Resume进行执行生效,如下图:
浏览器显示响应体的内容如下:
上传的是age为20,正常情况下响应体中的age应该也是20,但是因为经过了mitmproxy代理的修改,所以响应体报文中实际展示的是代理修改后的数据233。
mitmproxy代理启动时,若没有指定端口的话,默认占用的是8080端口,如果想指定其他端口,则可以添加
-p 端口号
指定端口,比如mitmweb -p 8089
表示mitmweb方式启动mitmproxy,mitmproxy监听的端口是8089
启动mitmproxy常用的参数有:
参数格式 | 说明 |
---|---|
-p 端口号 | 指定监听端口 |
-s xxx.py | 执行指定脚本 |
-w 文件完整路径 | 指定输出文件 |
-q quiet | 仅匹配脚本过滤后的数据包(即不输出不符合脚本的请求信息) |
"~m post | m表示method,当前表示仅匹配post请求 |
在教程:使用 mitmproxy + python 做拦截代理 中,有对请求的生命周期进行说明,但是我们经常用到的是如下2个:
def request(self, flow: mitmproxy.http.HTTPFlow):
def response(self, flow: mitmproxy.http.HTTPFlow):
本文主要对这2个事件进行展开。
我们先简单的入门下,了解事件是怎么使用的。以下是统计http或https的请求数:
count.py内容如下:
import mitmproxy.httpclass CountDemo:# 从请求的角度来统计http请求数量,如果想从响应的角度来统计的话,可以使用response()def request(self, flow: mitmproxy.http.HTTPFlow):# 如果对象中已经有num属性,则num属性加1if hasattr(self, "num"):self.num += 1else:# 如果对象中没有num属性,则直接赋属性值为1setattr(self, "num", 1)# 显示当前请求数量print(f"当前http/https的请求总数量为:{self.num}")# 变量名必须为addons,列表元素必须是相关实例(即对象)
addons = [CountDemo()
]
在count.py当前目录下,执行mitmweb -s count.py
命令,刷新浏览器页面,显示如下:
def request(self, flow: mitmproxy.http.HTTPFlow)
从请求数据开始处理,即请求发送到服务端前进行处理,以下是一些可能常用的属性说明:
属性 | 描述 |
---|---|
request = flow.request | 获取到request对象,对象包含了诸多属性,保存了请求的信息 |
request.url | 请求的url(字符串形式),修改url并不一定会生效,因为url是整体的,包含了host、path、query,最好从分体中修改 |
request.host | 请求的域名,字符串形式 |
request.headers | 请求头,Headers形式(类似于字典) |
request.content | 请求内容(byte类型) |
request.text | 请求内容(str类型) |
request.json() | 请求内容(dict类型) |
request.data | 请求信息(包含协议、请求头、请求体、请求时间、响应时间等内容) |
request.method | 请求方式,字符串形式,如POST、GET等 |
request.scheme | 协议,字符串形式,如http、https |
request.path | 请求路径,字符串形式,即url中除了域名之外的内容 |
request.query | url中的键值参数,MultiDictView类型的数据(类似于字典) |
request.query.keys() | 获取所有请求参数键值的键名 |
request.query.get(keyname) | 获取请求参数中参数名为keyname的参数值 |
示例:
demo.py
的内容如下:
import mitmproxy.httpclass Demo:def request(self, flow: mitmproxy.http.HTTPFlow):rq = flow.requestprint("---------------开始---------------")print(f"url:{rq.url}\n")print(f"host:{rq.host}\n")print(f"headers:{rq.headers}\n")print(f"method:{rq.method}\n")print(f"scheme:{rq.scheme}\n")print(f"path:{rq.path}\n")print(f"query:{rq.query}\n")print(f"content: {rq.content}\n")print(f"text: {rq.text}\n")print(f"json: {rq.json()}\n")print(f"data: {rq.data}\n")print("---------------结束---------------")addons = [Demo()
]
发送一个post请求:https://postman-echo.com/post?from=en&to=zh
命令行窗口打印如下:
接口说明:
postman-echo.com/post
接口支持任意的url参数和请求体设置,响应报文会根据上传的请求头、请求的url参数、请求体进行返回。
以下从几个示例中进行理解:
接口信息:
请求协议:https
域名:httpbin.org
路径:/get
参数:可以任意设置
响应数据:包含请求的请求头信息、请求参数信息和请求的其他信息
需求:修改接口的请求头内容,修改字段为:user-agent、content-type、cookie
脚本modify_headers
的内容如下:
import mitmproxy.httpclass ModifyDemo:def request(self, flow: mitmproxy.http.HTTPFlow):rq = flow.requestif "httpbin.org" in rq.url:rq.headers["user-agent"] = "listen in mitmproxy"rq.headers.update({"content-type": "personal make","cookie": "key1=value1; key2=value2"})
addons = [ModifyDemo()
]
mitmweb -s modify_headers.py
命令启动mitmproxy代理,并执行modify_headers.py脚本,对比未走代理和走了代理的结果如下:
需求:在百度搜索的内容,重定向到必应搜索
脚本redirect.py的内容如下:
import mitmproxy.httpclass RedirectDemo:def request(self, flow: mitmproxy.http.HTTPFlow):rq = flow.requestif "www.baidu.com" in rq.host and "wd=" in rq.url:sw = rq.query.get('wd')# 由于sw获取到的数据是ascii编码格式的内容,脚本默认的是utf-8格式的,所以url处理时需要将sw进行编码处理rq.url = f"https://cn.bing.com/search?q={sw.encode('utf-8')}"rq.host = "cn.bing.com"rq.query.clear()rq.query.update({"q": sw})addons = [RedirectDemo()
]
未启动代理时,百度搜索结果和必应搜索结果如下:
启动mitmproxy代理后,百度搜索被重定向到了必应搜索,显示了必应搜索结果页面
需求:修改postman-echo.com
请求体中的年龄,给httpbin.org
请求体增加gender字段
分析:我们可以从text、content、json()中获取请求体的内容,但是json()是个方法,所以无法从json()方法中进行请求体的修改,但我们可以从text和content这2个属性中进行修改
modify_req_body.py的内容如下:
# -*- coding:utf-8 -*-
# -*- coding:utf-8 -*-
import mitmproxy.http
import jsonclass ModifyReqBodyDemo:def request(self, flow: mitmproxy.http.HTTPFlow):req = flow.requestif "postman-echo.com" == req.host:jdata = req.json()jdata.update({"age": 26})req.text = json.dumps(jdata)if "httpbin.org" == req.host:con = req.contentreq.content = con.title() + ",学习进度70%".encode("utf-8")addons = [ModifyReqBodyDemo()
]
执行结果如下:
接口说明:
postman-echo.com/post
和httpbin.org/post
接口的逻辑是一样的,均支持任意的url参数和请求体设置,响应报文会根据上传的请求头、请求的url参数、请求体进行返回。
def response(self, flow: mitmproxy.http.HTTPFlow)
从获取到请求的响应数据开始处理,即代理收到服务端返回的响应数据,但是还没将数据发送给客户端的情况下进行处理,以下是一些可能常用的属性说明:
属性 | 描述 |
---|---|
response = flow.response | 获取到response对象,对象包含了诸多属性,保存了请求的响应信息 |
response.status_code | 响应码 |
response.text | 响应数据(str类型) |
response.content | 响应数据(Bytes类型) |
response.headers | 响应头,Headers形式(类似于字典) |
response.cookies | 响应的cookie |
response.set_text() | 修改 响应数据 |
response.get_text() | 响应数据(str类型) |
flow.response = flow.response.make(status_code, content, headers) | 设置响应信息 |
示例:
脚本resp_demo.py的内容如下:
import mitmproxy.httpclass Demo:def response(self, flow: mitmproxy.http.HTTPFlow):resp = flow.responseprint(f"类型:{type(resp.status_code)},内容:{resp.status_code}\n")print(f"类型:{type(resp.text)},内容:{resp.text}\n")print(f"类型:{type(resp.content)},内容:{resp.content}\n")print(f"类型:{type(resp.headers)},内容:{resp.headers}\n")print(f"类型:{type(resp.cookies)},内容:{resp.cookies}\n")addons = [Demo()
]
访问 https://httpbin.org/get?name=%E6%B8%A9%E5%B0%8F%E5%85%AB&age=18
的情况如下:
需求:将百度搜索结果中的”国足垃圾“修改成”中国男足垃圾“(女足好样的!!!)
modify_resp_body.py脚本内容如下:
import mitmproxy.httpclass ModifyRespBodyDemo:def response(self, flow: mitmproxy.http.HTTPFlow):req = flow.requestresp = flow.responseif "baidu.com" in req.url:t = resp.textnew = t.replace("国足垃圾", "中国男足垃圾")resp.text = newaddons = [ModifyRespBodyDemo()
]
代理执行前后变化如下:
我们知道响应状态码是status_code字段,但是客户端并不一定是根据status_code字段值去处理响应错误的情况,flow.response提供了flow.response.make(status_code, content, headers)
方法来修改响应信息
使用格式:
flow.response = flow.response.make(status_code=200, content=b"", headers=())
- status_code:响应状态码,int类型,默认为200
- content:响应报文体内容,可以传入字符串数据
- headers:响应头,可以传入字典数据
需求:如果百度搜索中包含敏感词,则返回违规说明;如果识别到是必应搜索,则直接返回404(
modify_resp_status.py脚本如下:
import mitmproxy.httpclass ModifyRespStatusDemo:def response(self, flow: mitmproxy.http.HTTPFlow):req = flow.requestif "baidu.com" in req.host and "wd" in req.query:if req.query.get("wd") in ["十八禁", "AV", "迷药", "杀人"]:flow.response = flow.response.make(status_code=400,content="""访问违规 涉及访问不健康内容,数据已上报
哔哩哔哩""",headers={"content-type": "text/html"})elif "cn.bing.com/" in req.host:flow.response = flow.response.make(404)# 以下的写法无效# resp = flow.response# resp = resp.make(404)addons = [ModifyRespStatusDemo()
]
执行结果如下:
上一篇:“乱弹琴”,歇后语的前句是什么
下一篇:真情实感的同义词