mitmproxy
用于 MITM 的 proxy,MITM 即中间人攻击(Man-in-the-middle attack)用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为。
*anyproxy这个工具也跟mitmproxy功能类似,不过是使用js编写脚本。 *
不同于 fiddler 或 wireshark 等抓包工具,mitmproxy 不仅可以截获请求帮助开发者查看、分析,更可以通过自定义脚本进行二次开发。举例来说,利用 fiddler 可以过滤出浏览器对某个特定 url 的请求,并查看、分析其数据,但实现不了高度定制化的需求,类似于:“截获对浏览器对该 url 的请求,将返回内容置空,并将真实的返回内容存到某个数据库,出现异常时发出邮件通知”。而对于 mitmproxy,这样的需求可以通过载入自定义 python 脚本轻松实现。
但 mitmproxy 并不会真的对无辜的人发起中间人攻击,由于 mitmproxy 工作在 HTTP 层,而当前 HTTPS 的普及让客户端拥有了检测并规避中间人攻击的能力,所以要让 mitmproxy 能够正常工作,必须要让客户端(APP 或浏览器)主动信任 mitmproxy 的 SSL 证书,或忽略证书异常,这也就意味着 APP 或浏览器是属于开发者本人的——显而易见,这不是在做黑产,而是在做开发或测试。
应用领域
目前比较广泛的应用是做仿真爬虫,即利用手机模拟器、无头浏览器来爬取 APP 或网站的数据,mitmproxy 作为代理可以拦截、存储爬虫获取到的数据,或修改数据调整爬虫的行为。
以上说的仅是 mitmproxy 以正向代理模式工作的情况,通过调整配置,mitmproxy 还可以作为透明代理、反向代理、上游代理、SOCKS 代理等,但这些工作模式针对 mitmproxy 来说似乎不大常用- selenium
有些时候我们也常配合selenium来用,规避掉selenium的行为,让其不被检测出浏览器是非正常操作。但是有些网站的反爬机制做的很好,即便我们有mitmproxy也是需要耗费很多精力。理论上只要分析对方网站反爬策略,我们就能用mitmproxy修改请求头和响应头来规避它。
- selenium
安装
注意需要是python3.6版本以上,所以对应的pip也是pip3版本
1
# 安装 python 的 mitmproxy 包除了会得到 mitmproxy 工具外,还会得到开发定制脚本所需要的包依赖
2
pip install mitmproxy
3
4
# 安装成功后拥有 mitmproxy、mitmdump、mitmweb 三个命令
启动
要启动 mitmproxy 用 mitmproxy、mitmdump、mitmweb 这三个命令中的任意一个即可,这三个命令功能一致,且都可以加载自定义脚本,唯一的区别是交互界面的不同。启动 mitmproxy 之后,默认开启8080端口
mitmproxy
mitmproxy 命令启动后(不支持windows系统),会提供一个命令行界面(使用它需要了解一些常用快捷键),用户可以实时看到发生的请求,并通过命令过滤请求,查看请求数据。
1
# 启动(启动后会显示Flows界面)
2
mitmproxy
3
#或者指定端口
4
mitmproxy -p 8888
5
6
# 如果希望在开一个终端专门查看命令,毕竟哪里记得住那么多命令,以下是不带服务的运行,单纯支持查看命令
7
mitmproxy -n # mitmproxy --no-server
常用快捷键
区分大小写,左右上下移动是直接支持键盘的上下左右键的,所以我们没必要记住那些快捷键(对应快捷键:hjkl),回车也是直接进入详情页面(对应快捷键:P)
常用的命令可以用快捷键,不用进入命令输入模式1
h/<-:左
2
l/->:右
3
k:上
4
j:下
5
P/enter:进入详情
6
tab:可以对栏目切换,或者命令补全
7
?:帮助界面,里面可以看到很多快捷键用法以及过滤的用法
8
::冒号是进入命令输入模式,可以输入的命令可以在Command Reference界面查看执行,按enter键执行命令
9
f:进入过滤操作
10
E:进入Events界面,在这里可以看到脚本的一些打印信息
11
C:进入命令参考界面(Command Reference),忘记命令的话可以来这里参考
12
O:进入Options界面
13
g:跳到第一行
14
G:跳到最后一行
15
m:标记某个网络请求,标记后会出现不一样的颜色
16
w:保存信息,一次可以保持多条信息(保存的信息完整)
17
e:只保存请求信息,不保存响应信息,一次只能保存一条网络请求信息(文件为文本文件)。不过在详情页的时候e按键是进入修改参数的
18
L:加载保存的信息,以供查看
19
z:清除flow的请求信息
20
n:新建一个flow
21
r:对这个请求重新发起请求
保存和载入操作
1
'''保存'''
2
# 按m进行标记后,在按w进入保存,@shown换成@marked表示只保存标记的
3
: save.file @marked /Users/Pocket/taobao_login.mitm
4
5
6
'''载入'''
7
# 另开一个mitmproxy的无服务来分析会更好
8
mitmproxy -n
9
# 按L键,输入路径回车即可
10
: view.flows.load /Users/Pocket/taobao_login.mitm
过滤表达式
按f进入过滤模式,常用过滤表达式可以按?获取
1
~u regex:过滤url,支持正则表达式
mitmdump
mitmdump 命令启动后没有界面,程序默默运行,所以 mitmdump无法提供过滤请求、查看数据的功能,只能结合自定义脚本,默默工作。
1
mitmdump
2
3
# 在启动时可以指定一个文件作为请求抓取信息的存储地
4
mitmdump -w outfile
mitmweb
mitmweb命令启动后,会提供一个web界面,用户可以实时看到发生的请求,并通过 GUI 交互来过滤请求,查看请求数据。
1
mitmweb
2
# 或者指定代理端口
3
mitmweb -p 8888
浏览器代理启动方式
浏览器如果需要使用mitmproxy,则在启动的时候需要指定代理。
- Google Chrome
- Mac系统
1
# 开一个终端启动mitmproxy,注意端口是否被占用,如果占用需要指定其他端口,本机测试发现8080没被占用也是代理不成功,估计被其他代理软件干扰了。
2
mitmweb -p 8888
3
# 在开一个终端启动浏览器,注意启动前需要把全部google chrome关闭,否则代理将无效
4
open -a /Applications/Google\ Chrome.app/ --args --proxy-server=127.0.0.1:8888 --ignore-certificate-errors
5
6
'''
7
指定代理规则访问网站:
8
--proxy-pac-url=file:///Users/pac.txt
9
'''
- windows
1
mitmweb
2
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --proxy-server=127.0.0.1:8080 --ignore-certificate-errors
iphone抓包
使用mitmproxy的时候需要在iPhone上安装CA证书,mitm.it,不过任何第三方这种信任证书还是建议使用玩删除即可,小心使得万年船。
- Mac系统
脚本编写
mitmproxy配合自己开发的自定义脚本才是它最强大的地方。脚本的编写需要遵循 mitmproxy规定的套路,这样的套路有两个,使用时选其中一个套路即可。
推荐第二种方法
- 第一个套路是,编写一个 py 文件供 mitmproxy 加载,文件中定义了若干函数,这些函数实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的函数
1
import mitmproxy.http
2
from mitmproxy import ctx
3
4
num = 0
5
6
7
def request(flow: mitmproxy.http.HTTPFlow):
8
global num
9
num = num + 1
10
ctx.log.info("We've seen %d flows" % num)
- 第二个套路是,编写一个 py 文件供 mitmproxy 加载,文件定义了变量 addons,addons 是个数组,每个元素是一个类实例,这些类有若干方法,这些方法实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的方法。这些类,称为一个个 addon。
1
import mitmproxy.http
2
from mitmproxy import ctx
3
4
5
class Counter:
6
def __init__(self):
7
self.num = 0
8
9
def request(self, flow: mitmproxy.http.HTTPFlow):
10
self.num = self.num + 1
11
ctx.log.info("We've seen %d flows" % self.num)
12
13
14
addons = [
15
Counter()
16
]
加载脚本方式
1 | mitmweb -p 8888 -s script.py |
2 | mitmproxy -s script.py |
3 | mitmdump -s script.py |
4 | # -q:屏蔽 mitmdump 默认的控制台日志,只显示自己脚本中的 |
5 | mitmdump -q -p 6666 -s mitmproxy_server.py |
6 | |
7 | # --set body-size-limit=10k: 只处理小于 10k 的请求 |
8 | # "~m post": 只处理 post 方法的请求 |
9 | mitmdump -q -s mitmproxy_server.py --set body-size-limit=10k "~m post" |
事件
事件针对不同生命周期分为 5 类。“生命周期”这里指在哪一个层面看待事件,举例来说,同样是一次 web 请求,我可以理解为“HTTP 请求 -> HTTP 响应”的过程,也可以理解为“TCP 连接 -> TCP 通信 -> TCP 断开”的过程。那么,如果我想拒绝来个某个 IP 的客户端请求,应当注册函数到针对 TCP 生命周期 的 tcp_start 事件,又或者,我想阻断对某个特定域名的请求时,则应当注册函数到针对 HTTP 声明周期的 http_connect 事件
针对 HTTP 生命周期
1
'''
2
收到了来自客户端的 HTTP CONNECT 请求。在 flow 上设置非 2xx 响应将返回该响应并断开连接。
3
CONNECT 不是常用的HTTP请求方法,目的是与服务器建立代理连接,仅是 client 与 proxy 的之间的交流,
4
所以 CONNECT 请求不会触发 request、response 等其他常规的 HTTP 事件。
5
'''
6
def http_connect(self, flow: mitmproxy.http.HTTPFlow):
7
pass
8
9
10
'''
11
来自客户端的 HTTP 请求的头部被成功读取。此时 flow 中的 request 的 body 是空的。
12
'''
13
def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
14
pass
15
16
17
'''
18
来自客户端的 HTTP 请求被成功完整读取。
19
'''
20
def request(self, flow: mitmproxy.http.HTTPFlow):
21
pass
22
23
24
'''
25
来自服务端端的 HTTP 响应被成功完整读取。
26
'''
27
def response(self, flow: mitmproxy.http.HTTPFlow):
28
pass
29
30
31
'''
32
发生了一个 HTTP错误。比如无效的服务端响应、连接断开等。
33
注意与“有效的 HTTP 错误返回”不是一回事,后者是一个正确的服务端响应,只是 HTTP code 表示错误而已。
34
'''
35
def error(self, flow: mitmproxy.http.HTTPFlow):
36
pass
针对 TCP 生命周期
1
'''
2
建立了一个 TCP 连接
3
'''
4
def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
5
pass
6
7
8
'''
9
TCP 连接收到了一条消息,最近一条消息存于 flow.messages[-1]。消息是可修改的
10
'''
11
def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):
12
pass
13
14
15
'''
16
发生了 TCP 错误
17
'''
18
def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
19
pass
20
21
22
'''
23
TCP 连接关闭
24
'''
25
def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
26
pass
针对 Websocket 生命周期
1
'''
2
客户端试图建立一个 websocket 连接。可以通过控制 HTTP 头部中针对 websocket 的条目来改变握手行为。flow 的 request 属性保证是非空的
3
'''
4
def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):
5
pass
6
7
8
'''
9
建立了一个 websocket 连接
10
'''
11
def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):
12
pass
13
14
15
'''
16
收到一条来自客户端或服务端的 websocket 消息。最近一条消息存于flow.messages[-1]。
17
消息是可修改的。目前有两种消息类型,对应 BINARY 类型的 frame 或 TEXT 类型的 frame
18
'''
19
def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
20
pass
21
22
23
'''
24
发生了 websocket 错误
25
'''
26
def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):
27
pass
28
29
30
'''
31
websocket 连接关闭
32
'''
33
def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):
34
pass
针对网络连接生命周期
1
'''
2
客户端连接到了 mitmproxy。注意一条连接可能对应多个 HTTP 请求
3
'''
4
def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):
5
pass
6
7
8
'''
9
客户端断开了和 mitmproxy 的连接
10
'''
11
def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):
12
pass
13
14
15
'''
16
mitmproxy 连接到了服务端。注意一条连接可能对应多个 HTTP 请求
17
'''
18
def serverconnect(self, conn: mitmproxy.connections.ServerConnection):
19
pass
20
21
22
'''
23
mitmproxy 断开了和服务端的连接
24
'''
25
def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):
26
pass
27
28
29
'''
30
网络 layer 发生切换。你可以通过返回一个新的 layer 对象来改变将被使用的 layer
31
'''
32
def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):
33
pass
通用生命周期
1
'''
2
配置发生变化。updated 参数是一个类似集合的对象,包含了所有变化了的选项。在 mitmproxy 启动时,该事件也会触发,且 updated 包含所有选项
3
'''
4
def configure(self, updated: typing.Set[str]):
5
pass
6
7
8
'''
9
addon 关闭或被移除,又或者 mitmproxy 本身关闭。由于会先等事件循环终止后再触发该事件,所以这是一个 addon 可以看见的最后一个事件。由于此时 log 也已经关闭,所以此时调用 log 函数没有任何输出。
10
'''
11
def done(self):
12
pass
13
14
15
'''
16
addon 第一次加载时。entry 参数是一个 Loader 对象,包含有添加选项、命令的方法。这里是 addon 配置它自己的地方。
17
'''
18
def load(self, entry: mitmproxy.addonmanager.Loader):
19
pass
20
21
22
'''
23
通过 mitmproxy.ctx.log 产生了一条新日志。小心不要在这个事件内打日志,否则会造成死循环。
24
'''
25
def log(self, entry: mitmproxy.log.LogEntry):
26
pass
27
28
29
'''
30
mitmproxy 完全启动并开始运行。此时,mitmproxy 已经绑定了端口,所有的 addon 都被加载了。
31
'''
32
def running(self):
33
pass
34
35
36
'''
37
一个或多个 flow 对象被修改了,通常是来自一个不同的 addon
38
'''
39
def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):
40
pass
使用mitmdump遇到的问题
CPU占用过大
在使用mitmdump命令运行监控脚本的时候,CPU经常飙升很高
1
1.-q 不输出日志节约资源
2
2.--ignore-hosts 利用正则过滤不必要的网址
3
4
# 最终命令可以是这样
5
mitmdump -q -p 6666 -s data_script/mitmproxy_server.py --set block_global=false --ignore-hosts '^(?![0-9\.]+:)(?!([^\.:]+\.)*test\.com)'
mitmproxy常见错误
因为mitmproxy采用的是select模式,所以连接上很占用文件句柄,很耗性能(占用CPU很夸张)。
以下报错虽然发生,但是还是可以抓请求,只是反应慢,CPU性能也不断飙升。
所以mitmproxy只适合个人测试抓取,不适合部署生产。1
# 经常会收到以下错误提示
2
1.OSError: [Errno 24] Too many open files
3
4
# 即便我们把linux进程能打开的文件句柄设的很大可以解决这个问题,但是还是会引发以下错误
5
# 我的这个错误引发环境是通过nginx进行TCP负载分发,而我的nginx使用的是epoll模式,导致后台的mitmproxy服务撑不住并发
6
2.ValueError: filedescriptor out of range in select()
7
8
"""
9
这是因为mitmproxy采用select模式,没用使用epoll模式。
10
linux中的select模式中单进程的限制为1024
11
"""
12
13
# 还容易发送如下错误,报的是连接已经中断
14
3.OSError: [Errno 107] Transport endpoint is not connected