什么是 mitmproxy
mitmproxy,顾名思义,就是用于MITM的proxy。MITM即中间人攻击(man-in-the-middle-attack)。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为。
不同于 fiddler 或 wireshark 等抓包工具,mitmproxy 不仅可以截获请求帮助开发者查看、分析,更可以通过自定义脚本进行二次开发。举例来说,利用 fiddler 可以过滤出浏览器对某个特定 url 的请求,并查看、分析其数据,但实现不了高度定制化的需求,类似于:“截获对浏览器对该 url 的请求,将返回内容置空,并将真实的返回内容存到某个数据库,出现异常时发出邮件通知”。而对于 mitmproxy,这样的需求可以通过载入自定义 python 脚本轻松实现。
mitmproxy 作为正向代理模式,可以用作仿真爬虫,在web测试中,也可以按需求修改response或者request,以方便测试人员测试一些比较难触发的异常场景。
如何安装
在linux 或者mac 中,可以直接运行:
sudo pip3 install mitmproxy
在windows中,以管理员身份运行cmd 或者power shell:
pip3 install mitmproxy
如何使用
要启动mitmproxy,可以用以下任一3个命令: mitmproxy, mitmdump, mitmweb. 这三个命令功能一致,且都可以加载自定义脚本,唯一的区别是交互界面的不同。
我就介绍一下我经常用到的: mitmweb 和 mitmdump。
mitmweb
mitmweb 命令启动后,会提供一个 web 界面,用户可以实时看到发生的请求,并通过 GUI 交互来过滤请求,查看请求数据。
mitmweb 使用场景
在编写rest api自动化脚本时,经常需要查看http 请求,这时候就可以首先启动一个web 服务, 命令如下: mitmweb --ssl-insecure -p 8999 (使用--ssl-insecure 是为了忽略不被信任的自签名证书):
这时候会同时打开一个web 页面:
然后脚本里进行如下配置:
test_bot:
host: https://tec-l-1183620.labs.microstrategy.com/MicroStrategyLibrary
user:
name: mstr1
password: newman1
locale: en_us
id: 54F3D26011D2896560009A8E67019608
proxies:
http: http://127.0.0.1:8999
https: http://127.0.0.1:8999
projectId:
- B7CA92F04B9FAE8D941C3E9B7E0CD754
dossier:
- 6A0470264C4D68CD5308FFB1CB198FA5
requests_response = requests.request(
method=self.method,
url=self.host + self.path,
headers=self.headers,
params=self.query,
data=raw,
proxies=self.proxies,
verify=False
)
r = Response(requests_response)
配置完成后,就能在web 页面看到所有相关的http请求了。debug 脚本的时候就可以不需要打断点去查看相关请求, 着实高效了不少。
mitmdump
mitmdump命令启动后没有界面,程序默默运行,所以 mitmdump 无法提供过滤请求、查看数据的功能,只能结合自定义脚本,默默工作。
mitmdump 配合自定义脚本,就可以发挥它强大的脚本二次开发能力了。
使用-s 可以指定一个脚本来处理接获的数据: mitmdump -s script.py
mitmdump 使用场景1:rewrite and map
测试长字符串。修改ai service 返回的推荐内容。推荐内容是根据当前的数据集进行推荐的,如果要测试长文本的话,就会比较难。一种方式就是hack response来mock 长文本。
修改custom app 的contentType 这个参数,这个参数是不被暴露出来的,如果想要修改需要调用api,过程比较繁琐。
测试异常响应时前端表现。比如当aiService 返回400 或者500 error时前端表现。
具体代码如下:
def response(flow: http.HTTPFlow):
if "/api/aiservice/chats/recommendations/dossier" in flow.request.path:
with open('/Users/xuyin/Downloads/1.txt', 'r') as f:
text = f.read()
data = json.loads(flow.response.content)
data['result']['insights'][0] = text
flow.response.text = json.dumps(data)
if '/api/v2/applications?outputFlag=FILTER_AUTH_MODES' in flow.request.path:
data = json.loads(flow.response.content)
for item in data['applications']:
if item['name'] == 'MicroStrategy':
item['homeScreen']['contentType']['bot'] = False
flow.response.text = json.dumps(data)
if 'api/aiservice/chats/dossier' in flow.request.path:
flow.response.status_code = 400
data = json.loads(flow.response.content)
data['message'] = 'Sorry AI service reports an error'
flow.response.text = json.dumps(data)
结果如下:
未经mock之前,返回建议中的相关建议字数都不长。
mock之后,第一条已经被更新。
未经mock之前,library的主页面可以看到机器人的选项。
mock之后,机器人的选项在侧边栏已消失。
未经mock之前
mock之后,当模拟400 error的时候,response body里的message已经被更新,status code也从原来的200变成了400。
mitmdump 使用场景2:爬虫
除此之外, 使用mitmproxy 也可以实现网页页面爬虫的提取功能:
比如我想抓取一下页面的所有的问题,回复已经回复时间。当消息很多的时候,一条一条复制粘贴显然太低效了。
这时候只需要编写脚本然后运行以下命令(使用-w logfile 将输出存储到指定文件)
mitmdump -s /Users/xuyin/PycharmProjects/ceshkaifa/mock/demo.py --ssl-insecure -p 8999 -w ~/Downloads/answers.txt
把所有抓取到的信息存到指定文件中,就可以了。
脚本如下:
def response(flow: http.HTTPFlow):
if '/api/bots/BD985181654E0D61180C5DA4F2DE3629/chats' in flow.request.path:
ctx.log.info("-------start to catch chat messages-------\n")
data = json.loads(flow.response.content)
cnt = 1
ctx.log.info(f"-------{data['chats'][0]['messages']}-------\n")
for item in data['chats'][0]['messages']:
question = item['question']['text']
answers = "\n".join(answer['text'] for answer in item['answers'])
creation_date = item['creationDate']
ctx.log.info(f"-------{cnt}-------\n")
ctx.log.info(f"-------question: {question}-------\n")
ctx.log.info(f"-------answer: {answers}-------\n")
ctx.log.info(f"-------creation_date: {creation_date}-------\n")
cnt += 1
最后获取到内容如下:
其他代理模式
讲了这么多正向代理模式,mitmproxy 还支持其他的代理,比反向代理,上行代理, 透明代理和socks5代理。这里我就简单介绍下如何使用mitmproxy进行反向代理:
众所周知,nginx 也支持反向代理,但是配置略为繁琐,比如openai 的api endpoint 国内网络是无法直接访问的,如果有一个跳板机,那么只需要在跳板机上运行mitmproxy 反向代理模式,那么即使本机不挂代理,也可以轻轻松松访问openai 的api 了。
通过使用 --mode reverse 就可以指定代理模式为反向代理(默认为正向代理模式)
在跳板机运行以下命令:
mitmdump -p 8090 --mode reverse:https://api.openai.com/
然后在jupternotebook 里修改环境变量,把OPENAI_API_BASE改成代理的地址,就大功告成了。
对比nignx 对于转发的配置,mitmproxy只用一句话就能搞定,还是非常方便的。
http{
log_format main '$remote_addr - [$time_local] '
'$status ';
access_log /var/log/nginx/access_http.log main;
server{
listen 8060;
server_name 10.23.35.208;
location / {
proxy_pass https://api.openai.com/;
proxy_ssl_server_name on;
proxy_set_header Host api.openai.com;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
以上只是mitmproxy的基本使用方式,mitmproxy 的github repo 本身也提供了很多addons 可以直接进行调用,mitmproxy 配合selenium 就可以实现更多的网页爬虫功能,感兴趣的小伙伴可以自行搜索。