mitmproxy是一款开源的HTTP/HTTPS/WebSocket代理拦截工具,它能很方便的实现HTTP请求拦截、修改和重放。mitmproxy和一般的软件工具有些区别,它本质上类似于一个Python框架,不过它也提供了CLI界面和Web界面方便用户使用。
mitmproxy的功能和BurpSuite是重合的,但两者也有一些区别。首先,毫无疑问肯定是BurpSuite的功能更为强大,毕竟mitmproxy是开源项目,而BurpSuite是收费的商业软件;不过mitmproxy的一个优势是它提供了扩展机制,能够很方便的集成Python代码拦截和修改HTTP请求,BurpSuite要实现类似功能则需要使用Java代码编写插件,操作起来更复杂一些,因此mitmproxy设计的其实对程序员更友好。
Github地址:https://github.com/mitmproxy/mitmproxy
我们这里直接以Python库的形式使用mitmproxy,首先创建Python虚拟环境。
mkdir mitm-demo && cd mitm-demo && python3 -m venv ./venv/
然后激活当前虚拟环境,安装mitmproxy即可。
pip3 install mitmproxy
mitmproxy其实有3种使用方式,mitmproxy
、mitmweb
和mitmdump
。
mitmproxy
和mitmweb
分别是启动CLI界面和Web界面,它们本质上是相同的,都提供了操作mitmproxy的各项配置和开关的界面。CLI界面虽然看起来更炫酷但用起来确实比较复杂,除非我们在没有图形界面的服务器上否则还是用Web界面比较方便,我们后面还是主要以Web界面为例进行介绍。
至于mitmdump
,它不启动任何界面,该启动方式主要用于加载我们通过脚本扩展mitmproxy的场景。
我们这里以mitmproxy提供的Web界面为例进行介绍,首先我们需要启动mitmproxy服务。
mitmweb
默认情况下,mitmproxy代理服务会监听本机所有网络接口的8080
端口,Web界面则启动在8081
端口。服务启动成功后,我们访问http://127.0.0.1:8081
可以看到界面。
这里我们使用curl
工具简单测试一下。假设我们要访问网站http://127.0.0.1:18080/demo
,这里我们使用mitmproxy作为代理,我们这里使用--proxy
参数(注:我这里使用的是Linux操作系统,Windows操作系统的curl
功能不全,可能不支持--proxy
参数)。
curl --proxy "http://127.0.0.1:8080" "http://127.0.0.1:18080/demo"
返回mitmproxy的Web界面,可以看到我们的请求已经成功被记录。这个界面类似于Chrome浏览器的开发者工具,点击其中记录的请求记录,我们可以看到HTTP请求、响应的详细信息。
在请求记录中,我们点击重放按钮,即可再次发出请求。
此外,我们也可以对请求进行编辑,然后再进行重放。
在界面顶部我们可以看到Intercept
功能输入框,它可以用来拦截请求。请求被拦截后会暂停,我们可以对其进行修改等操作,然后再放行请求。我们这里输入~u 127.0.0.1:18080
,表示拦截所有URL中包含该字符串的请求。
我们还是使用curl
工具发起请求,不过这里我们发起一个POST请求。
curl --proxy "http://127.0.0.1:8080" -X POST "http://127.0.0.1:18080/demo"
请求发出后,由于我们配置了Intercept规则,可以看到此时请求被拦截了,它处于“暂停”状态。
此时我们可以对请求进行修改,比如修改它的Body。
修改完成后,我们可以点击“继续”按钮,让请求完成。
此时服务端收到的就是被修改后的请求了。
我们知道HTTPS协议是需要证书的,mitmproxy作为七层代理如果要处理HTTPS协议就必须向系统导入它的证书。在使用mitmproxy代理的情况下,访问http://mitm.it/,选择对应的操作系统(和浏览器)并按照说明安装即可。
前面我们介绍了mitmproxy的基本使用方式,这里我们继续深入,介绍如何使用Python脚本扩展mitmproxy的功能。mitmproxy实现了插件机制(Addons),插件通过事件钩子函数(Event Hooks)与mitmproxy交互,我们可以通过自定义插件的方式扩展mitmproxy的功能,实际上mitmproxy中很多内部功能也都是通过内置插件实现的。
下面是一个最简单的插件实现,脚本中我们定义了Counter类,它的功能是每次处理HTTP请求时都将计数值加1,其中request()
方法是mitmproxy支持的一个HTTP Event钩子函数;此外我们还定义了变量addons
数组,稍后mitmproxy会自动根据变量名加载它。
import logging
class Counter:
def __init__(self):
self.num = 0
def request(self, flow):
self.num += 1
logging.info("We've seen %d flows" % self.num)
addons = [Counter()]
使用如下命令启动mitmproxy,其中-s
参数指定脚本的位置。这里我们就不需要打开任何界面了,因此使用mitmdump
命令。
mitmdump -s demo01.py
如果我们的扩展脚本被正常加载,当使用mitmproxy代理访问时,可以看到类似如下的输出。
[12:02:01.548] We've seen 11 flows
[12:02:01.549] We've seen 12 flows
看到这里我想大家已经明白如何扩展mitmproxy的功能了,其实就是基于Event钩子编写自定义逻辑并封装成插件类,随着mitmproxy启动加载到框架中。有关更多其它的钩子函数,这里就不逐一列举了,我们可以参考官方文档中对应的章节:https://docs.mitmproxy.org/stable/api/events.html。
有时我们的扩展脚本需要用户传入一些配置参数,这些也可以通过mitmproxy提供的扩展机制实现。下面例子中我们读取两个命令行参数header_name
和header_value
用于修改HTTP请求头。
import logging
from mitmproxy import ctx
class ModHeader:
def load(self, loader):
loader.add_option(
name="header_name",
typespec=str,
default="",
help="Header name",
)
loader.add_option(
name="header_value",
typespec=str,
default="",
help="Header value",
)
def request(self, flow):
if ctx.options.header_name and ctx.options.header_value:
flow.request.headers[ctx.options.header_name] = ctx.options.header_value
logging.info(flow.request.headers)
addons = [ModHeader()]
参数可以通过--set
设置,下面是一个例子。
mitmdump -s demo01.py --set "header_name=env" --set "header_value=beta"
使用该命令运行mitmproxy后,我们可以在服务端中看到请求头被附加了env: beta
。
前面我们提到过,mitmproxy包含了一系列的内置插件。实际上“添加请求头”这种简单功能已经由内置插件实现了,有关内置插件我们可以查看mitmproxy的官方Github仓库:https://github.com/mitmproxy/mitmproxy/tree/main/mitmproxy/addons