mitmproxy的安装
基本使用:mitmproxy mitmdump mitmweb

mitmproxy


mitmproxy 就是用于 MITM 的 proxy,MITM 即中间人攻击(Man-in-the-middle attack)。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为。
不同于 fiddler 或 wireshark 等抓包工具,mitmproxy 不仅可以截获请求帮助开发者查看、分析,更可以通过自定义脚本进行二次开发。举例来说,利用 fiddler 可以过滤出浏览器对某个特定 url 的请求,并查看、分析其数据,但实现不了高度定制化的需求,类似于:“截获对浏览器对该 url 的请求,将返回内容置空,并将真实的返回内容存到某个数据库,出现异常时发出邮件通知”。而对于 mitmproxy,这样的需求可以通过载入自定义 python 脚本轻松实现。
那这样的工具有什么实际意义呢?目前比较广泛的应用是做仿真爬虫,即利用手机模拟器、无头浏览器来爬取 APP 或网站的数据,mitmpproxy 作为代理可以拦截、存储爬虫获取到的数据,或修改数据调整爬虫的行为。

相关链接


mitmproxy 的安装


这里以macOS和Android手机对接为例

  1. mac下的安装
brew install mitmproxy

安装完成后可得到mitmproxy、mitmdump、mitmweb 三个命令(但mitmproxy命令不支持在windows系统中运行),这里使用mitmdump来校验是否安装成功

$ mitmdump --version
Mitmproxy: 5.2
Python:    3.8.5
OpenSSL:   OpenSSL 1.1.1g  21 Apr 2020
Platform:  macOS-10.16-x86_64-i386-64bit
  1. 证书配置
    如果想要截获 HTTPS 请求,就需要设置证书。mitmproxy 在安装后会提供一套 CA 证书,只要客户端信任了 mitmproxy 提供的证书,就可以通过 mitmproxy 获取 HTTPS 请求的具体内容,否则 mitmproxy 是无法解析 HTTPS 请求的
mitmdump

然后在用户目录下的.mitmproxy目录里面找到 CA 证书(各个平台),Mac 下双击 mitmproxy-ca-cert.pem 即可弹出钥匙串管理页面,然后找到 mitmproxy 证书,打开其设置选项,选择 “始终信任” 即可

  1. android手机信任证书
adb push ~/.mitmproxy/mitmproxy-ca-cert.pem /sdcard

各手机安装pem证书方式不一样,如小米的是:更多设置—系统安全—加密与凭据—从存储设备安装,选择已经导入sdcard中的证书

  1. 安装pypi包
    如果还要利用 Python 对 HTTP 请求和响应进行实时处理,则需要在对应的python环境安装mitmproxy包
pip install mitmproxy

其他平台可参看官方文档崔庆才的博客-mitmproxy 的安装

基本使用


mitmproxy的使用

mitmproxy 命令启动后,会提供一个命令行界面,用户可以实时看到发生的请求,并通过命令过滤请求,查看请求数据(以手机为例)

  • 手机与电脑在同一个局域网内
  • 手机wifi设置代理,端口8080
  • mitmproxy命令行界面便会呈现出手机上的所有请求

[64/161]:共161个请求,当前箭头指向第64个
get/post及请求的 URL
响应状态码200,响应内容的类型,如 text/html 代表网页文档、image/gif 代表图片
响应体的大小和响应的时间

  • 一些操作:
    1. 查看某个请求的详情:敲击回车,进入请求的详情页面; 可以看到Headers 的详细信息,如 Host、Cookies、User-Agent 等。
    2. 切换最上方是一个 Request、Response、Detail 的列表tab
    3. Response包含响应头和响应体
    4. Detail包含当前请求的详细信息,如服务器的 IP 和端口、HTTP 协议版本、客户端的 IP 和端口
    5. 其他更高级的用法可参看官方文档,如修改请求和请求回放等

以上介绍的功能,Fiddler、Charles 也有这个功能,而且它们的图形界面操作更加方便。那么 mitmproxy 的优势何在?mitmproxy 的强大之处体现在它的另一个工具 mitmdump,有了它我们可以直接对接 Python 对请求进行处理

mitmweb的使用

$ mitmweb
Web server listening at http://127.0.0.1:8081/
Proxy server listening at http://*:8080

mitmweb命令启动后,会提供一个 web 界面,用户可以实时看到发生的请求,并通过 GUI 交互来过滤请求,查看请求数据

mitmdump的使用

  1. 截获的数据保存到文件中
    mitmdump -w outfile
    
  2. 指定一个脚本来处理截获的数据
    mitmdump -s script.py
    

    这里指定了当前处理脚本为 script.py,它需要放置在当前命令执行的目录下。

  3. script.py怎么处理数据?
  • 处理请求
# 例1:修改请求头,script.py内容如下
def request(flow): #   flow,它其实是一个 HTTPFlow 对象,通过 request 属性即可获取到当前请求对象
   flow.request.headers['User-Agent'] = 'MitmProxy' # 将请求头的 User-Agent 修改成了 MitmProxy
   print(flow.request.headers) 
  • 日志输出:ctx 模块,它有一个 log 功能,调用不同的输出方法就可以输出不同颜色的结果,以方便我们做调试。例如,info 方法输出的内容是白色的,warn 方法输出的内容是黄色的,error 方法输出的内容是红色的
from mitmproxy import ctx
def request(flow):
   flow.request.headers['User-Agent'] = 'MitmProxy'
   ctx.log.info(str(flow.request.headers))
   ctx.log.warn(str(flow.request.headers))
   ctx.log.error(str(flow.request.headers))
  • 请求request中的其他属性
from mitmproxy import ctx
def request(flow):
   request = flow.request
   info = ctx.log.info
   info(request.url)
   info(str(request.headers))
   info(str(request.cookies))
   info(request.host)
   info(request.method)
   info(str(request.port))
   info(request.scheme)

Request 的一些常见属性,如 URL、Headers、Cookies、Host、Method、Scheme 等,同样,我们也可以对其进行修改处理

  • response处理
from mitmproxy import ctx
def response(flow):
   response = flow.response
   info = ctx.log.info
   info(str(response.status_code))
   info(str(response.headers))
   info(str(response.cookies))
   info(str(response.text))

可以看到输出了响应的 status_code、headers、cookies、text 这几个属性

实战应用


在爬虫项目中,经常碰到请求中包含一些加密的token或sign,一般很难破解。这时我们则可使用mitmproxy的mitmdump的结合appium/airtest/selenium操作网页或者app后来实时获取响应并处理获取需要的数据,而不需要关心加密的token或sign

# 一个script.py案例
from mitmproxy import ctx
import json
def response(flow):
    response = flow.response
    if response.status_code != 200:
        return
    data = json.loads(str(response.text))
    for item in data.get('results'):
        name = item.get('name')
        with open(f'{name}.json', 'w', encoding='utf-8') as f:
            f.write(json.dumps(item, indent=2, ensure_ascii=False))

参考