Mitmproxy
mitmproxy 就是用于 MITM 的 proxy,MITM 即中间人攻击(Man-in-the-middle attack)。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为。
特点
- 和正常的代理一样转发请求,保障服务端、客户端运行
- 拦截请求、修改请求、拦截返回、修改返回
- 载入自定义Python脚本
三大组件
- mitmproxy
- mitmdump
- mitmweb
安装
$ mitmweb --version
Mitmproxy: 4.0.4
Python: 3.7.3
OpenSSL: OpenSSL 1.1.0i 14 Aug 2018
Platform: Windows-10-10.0.17763-SP0
$ mitmdump --version
Mitmproxy: 4.0.4
Python: 3.7.3
OpenSSL: OpenSSL 1.1.0i 14 Aug 2018
Platform: Windows-10-10.0.17763-SP0
安装证书
前提要启动服务
mitmproxy -p 8200 --set block_global=false
将浏览器切换代理8080端口
http://mitm.it/
下载相关平台的新人证书然后导入,即可。
运行
要启动 mitmproxy 用 mitmproxy
、mitmdump
、mitmweb
这三个命令中的任意一个即可,这三个命令功能一致,且都可以加载自定义脚本,唯一的区别是交互界面的不同。
命令
mitmproxy
-p // 设置监听端口号
--set block_global=false // 公网环境需要设置否则失败连接
mitmproxy是一个控制台工具 使用?
快捷键从任何mitmproxy屏幕上查看帮助文档
过滤表达式f
快捷键f
~a | 过滤返回来的资源比如 CSS, Javascript, Flash, images. |
---|---|
~b regex | Body |
~bq regex | Request body |
~bs regex | Response body |
~c int | HTTP response code |
~d regex | 域名正则 |
~dst regex | Match destination address |
~e | Match error |
~h regex | Header |
~hq regex | Request header |
~hs regex | Response header |
~http | Match HTTP flows |
~m regex | Method |
~marked | Match marked flows |
~q | Match request with no response |
~s | Match response |
~src regex | Match source address |
~t regex | Content-type header |
~tcp | Match TCP flows |
~tq regex | Request Content-Type header |
~ts regex | Response Content-Type header |
~u regex | URL |
~websocket | Match WebSocket flows |
! | not |
& | and |
| | or |
(...) | grouping |
- 正则表达式是Python风格的
例子
过滤只有百度的域名
按f
然后输入过滤规则~d baidu.com
过滤请求方法为Post且域名为包含baidu的数据流
~d baidu.com % ~m post
断点拦截i
hint type=“info” title=“快捷键”
快捷键i
/hint
请求拦截
比如想要拦截itaolaity.com
的数据
然后停留到这里来了,且时间为红色标记
按下回车查看详细信息。且按下e
选择要修改的参数
比如
cookies
url
...
最后返回界面按下a
继续往下走。
脚本 - mitmdump
编写一个py文件提供给mitmproxy加载。
文件中定义了若干函数,这些函数实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的函数
或者 编写一个 py 文件供 mitmproxy 加载,文件定义了变量 addons,addons 是个数组,每个元素是一个类实例,这些类有若干方法,这些方法实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的方法。这些类,称为一个个 addon
,比如一个叫 Counter 的 addon
import mitmproxy.http
from mitmproxy import ctx
class Counter:
def __init__(self):
self.num = 0
def request(self, flow: mitmproxy.http.HTTPFlow):
self.num = self.num + 1
ctx.log.info("We've seen %d flows" % self.num)
addons = [
Counter()
]
启动
mitmdump -p 8080
编写一个py脚本用来供mitmproxy加载。
实现mitmproxy提供的事件,mitmproxy会在某个事件发生调用相关函数。
mitmdump
-p 指定端口号
-s 指定py脚本
-w 指定写入文件 默认将所有流量写入outfile
测试
import mitmproxy.http # http处理模块
from mitmproxy import ctx # 日志模块
def request(flow):
print("URL: \t"+flow.request.url)
ctx.log.info(str(flow.request.headers)) # 请求头
ctx.log.warn(str(flow.request.path))
λ mitmdump -p 8080 -s main.py
Loading script main.py # 加载脚本
Proxy server listening at http://*:8080 #监听端口
127.0.0.1:14072: clientconnect
URL: https://sug.so.360.cn/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=
Headers[(b'Host', b'sug.so.360.cn'), (b'Connection', b'keep-alive'), (b'Sec-Fetch-Site', b'none'), (b'Sec-Fetch-Mode', b'no-cors'), (b'User-Agent', b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36'), (b'Accept-Encoding', b'gzip, deflate, br'), (b'Accept-Language', b'zh,zh-CN;q=0.9,en;q=0.8'), (b'Cookie', b'__huid=110y7q8GOJVD4AeHprC/qVgeaKmeZo0r/0O1mg48NrWBQ=; __guid=209084363.1328179603006700032.1557537740001.3984; __gid=156009789.758223608.1572875073297.1572875076825.4; __DC_gid=156009789.758223608.1572875073297.1573629455750.9')]
/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=
127.0.0.1:14072: GET https://sug.so.360.cn/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=
<< 200 OK 7b
URL: https://sug.so.360.cn/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=i
Headers[(b'Host', b'sug.so.360.cn'), (b'Connection', b'keep-alive'), (b'Sec-Fetch-Site', b'none'), (b'Sec-Fetch-Mode', b'no-cors'), (b'User-Agent', b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36'), (b'Accept-Encoding', b'gzip, deflate, br'), (b'Accept-Language', b'zh,zh-CN;q=0.9,en;q=0.8'), (b'Cookie', b'__huid=110y7q8GOJVD4AeHprC/qVgeaKmeZo0r/0O1mg48NrWBQ=; __guid=209084363.1328179603006700032.1557537740001.3984; __gid=156009789.758223608.1572875073297.1572875076825.4; __DC_gid=156009789.758223608.1572875073297.1573629455750.9')]
/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=i
定义了一个request()方法。参数为flow,它其实是一个HTTPFlow对象。通过request属性即可获取到当前请求对象。
日志输出
from mitmproxy import ctx
可以设定不同级别的不同颜色的结果
ctx.log.info() // 白色
...warn() // 黄色
...error()// 红色
调用ctx模块
Request对象
Request对象什么功能呢?
例子 - 常用属性
import mitmproxy.http
from mitmproxy import ctx # 日志模块
def request(flow):
info = ctx.log.info
request = flow.request
print("\n\n+++++++++++++++++ 开始 ++++++++++++++++++++++")
info("URL: \t"+request.url)
info("Headers: \t" + str(request.headers))
info("Cookies: \t"+ str(request.cookies))
info("Method: \t" + request.method)
info("Port: \t"+ str(request.port))
info("Scheme: \t" + request.scheme)
print("+++++++++++++++++++++++++++++++++++++++\n\n")
URL: https://blog.itaolaity.com/usr/plugins/Mirages/biaoqing/paopao/E5A4A7E68B87E68C87_2x.png
Headers: Headers[(b'Host', b'blog.itaolaity.com'), (b'Connection', b'keep-alive')
Cookies: MultiDictView[['crisp-client%2Fsession%2F1e699570-3cf8-4358-a...
Method: GET
Port: 443
Scheme: https
伪造站点
import mitmproxy.http
def request(flow):
url = 'https://blog.itaolaity.com/'
if 'itaolaity.com' not in flow.request.url:
flow.request.url = url
可以看到无论访问什么网站,都会访问指定url但是上面的域名还是要访问的。
Response对象
响应内容,Response Body才是爬取的结果。
测试
import mitmproxy.http
def request(flow):
pass
def response(flow):
response = flow.response
print(response.status_code)
print("响应内容: \t" + response.text)
事件
事件针对不同生命周期分为 5 类 。
同样是一次 web 请求,我可以理解为“HTTP 请求 -> HTTP 响应”的过程,也可以理解为“TCP 连接 -> TCP 通信 -> TCP 断开”的过程。那么,如果我想拒绝来个某个 IP 的客户端请求,应当注册函数到针对 TCP 生命周期 的 tcp_start
事件,又或者,我想阻断对某个特定域名的请求时,则应当注册函数到针对 HTTP 声明周期的 http_connect
事件
http周期
def http_connect(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 收到了来自客户端的 HTTP CONNECT 请求。在 flow 上设置非 2xx 响应将返回该响应并断开连接。CONNECT 不是常用的 HTTP 请求方法,目的是与服务器建立代理连接,仅是 client 与 proxy 的之间的交流,所以 CONNECT 请求不会触发 request、response 等其他常规的 HTTP 事件。
def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 来自客户端的 HTTP 请求的头部被成功读取。此时 flow 中的 request 的 body 是空的
def response(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 来自服务端端的 HTTP 响应被成功完整读取
def error(self, flow: mitmproxy.http.HTTPFlow):
(Called when) 发生了一个 HTTP 错误。比如无效的服务端响应、连接断开等。注意与“有效的 HTTP 错误返回”不是一回事,后者是一个正确的服务端响应,只是 HTTP code 表示错误而已
tcp周期
# 建立了一个 TCP 连接
def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
# TCP 连接收到了一条消息
def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):
# 发生了 TCP 错误
def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
# TCP 连接关闭
def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
Websocket 生命周期
def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):
def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):
def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):
def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):
网络连接生命周期
# 客户端连接到了 mitmproxy。注意一条连接可能对应多个 HTTP 请求
def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):
# 客户端断开了和 mitmproxy 的连接
def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):
def serverconnect(self, conn: mitmproxy.connections.ServerConnection):
def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):
def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):
参考
- 官方文档
https://docs.mitmproxy.org/stable/concepts-filters/
- 官网
- 博客