您当前的位置:首页 > 计算机 > 编程开发 > Python

app抓包之mitmproxy的安装和使用

时间:08-21来源:作者:点击数:
城东书院 www.cdsy.xyz

mitmproxy是一个支持HTTP和HTTPS的抓包程序,类似Fiddler、Charles的功能,只不过它通过控制台的形式操作。

此外,mitmproxy还有两个关联组件,一个是mitmdump,它是mitmproxy的命令行接口,利用它可以对接Python脚本,实现监听后的处理;另一个是mitmweb,它是一个Web程序,通过它以清楚地观察到mitmproxy捕获的请求。

linux下借助pip,可以一键安装:pip install mitmproxy

windows 下需要安装Microsoft Visual C++ V14.0以上,之后再使用pip install mitmproxy安装

mitmproxy有三大组件:

  • mitmproxy - linux下的抓包组件
  • mitmdump - python交互
  • mitmweb - windows下的可视化界面工具

在windows下仅支持后两种组件的使用。

本节中,我们就来了解一下mitmproxy、mitmdump和mitmweb的安装方式。

相关链接

安装

pip安装

最简单的安装方式还是使用pip,直接执行如下命令即可安装:

pip3 install mitmproxy

这是最简单和通用的安装方式,执行完毕之后即可完成mitmproxy的安装,另外还附带安装了mitmdump和mitmweb这两个组件。如果不想用这种方式安装,也可以选择后面列出的专门针对各个平台的安装方式或者Docker安装方式。

Windows下的安装

可以到GitHub上的Releases页面(链接为:https://github.com/mitmproxy/mitmproxy/releases/)获取安装包,如图1-59所示。

图1-59 下载页面

比如,当前的最新版本为2.0.2,则可以选择下载Windows下的exe安装包mitmproxy-2.0.2-windows-installer.exe,下载后直接双击安装包即可安装。

注意,在Windows上不支持mitmproxy的控制台接口,但是可以使用mitmdump和mitmweb。

Linux下的安装

在Linux下,可以下载编译好的二进制包(下载地址https://github.com/mitmproxy/mitmproxy/releases/),此发行包一般是最新版本,它包含了最新版本的mitmproxy和内置的Python 3环境,以及最新的OpenSSL环境。

如果你的环境里没有Python 3和OpenSSL环境,建议使用此种方式安装。

下载之后,需要解压并将其配置到环境变量:

tar  -zxvf mitmproxy-2.0.2-linux.tar.gz
sudo mv mitmproxy mitmdump mitmweb  /usr/bin

这样就可以将3个可执行文件移动到了/usr/bin目录。而一般情况下,/usr/bin目录都已经配置在了环境变量下,所以接下来可以直接调用这3个工具了。

Mac下的安装

Mac下的安装非常简单,直接使用Homebrew即可,命令如下:

brew install mitmproxy

执行命令后,即可完成mitmproxy的安装。

Docker安装

mitmproxy也支持Docker,其DockerHub的地址为https://hub.docker.com/r/mitmproxy/mitmproxy/

在Docker下,mitmproxy的安装命令为:

docker run --rm -it -p 8080:8080 mitmproxy/mitmproxy mitmdump

这样就在8080端口上启动了mitmproxy和mitmdump。

如果想要获取CA证书,可以选择挂载磁盘选项,命令如下:

docker run --rm -it -v ~/.mitmproxy:/home/mitmproxy/.mitmproxy -p 8080:8080 mitmproxy/mitmproxy mitmdump

这样就可以在~/.mitmproxy目录下找到CA证书。

另外,还可以在8081端口上启动mitmweb,命令如下:

docker run --rm -it -p 8080:8080 -p 127.0.0.1:8081:8081 mitmproxy/mitmproxy mitmweb

更多启动方式可以参考Docker Hub的安装说明。

证书配置

对于mitmproxy来说,如果想要截获HTTPS请求,就需要设置证书。mitmproxy在安装后会提供一套CA证书,只要客户端信任了mitmproxy提供的证书,就可以通过mitmproxy获取HTTPS请求的具体内容,否则mitmproxy是无法解析HTTPS请求的。

首先,运行以下命令产生CA证书,并启动mitmdump:

mitmdump

接下来,我们就可以在用户目录下的.mitmproxy目录里面找到CA证书,如图1-60所示。

图1-60 证书文件

证书一共5个,表1-1简要说明了这5个证书。

  • 表1-1 5个证书及其说明
名称 描述
mitmproxy-ca.pem PEM格式的证书私钥
mitmproxy-ca-cert.pem PEM格式证书,适用于大多数非Windows平台
mitmproxy-ca-cert.p12 PKCS12格式的证书,适用于Windows平台
mitmproxy-ca-cert.cer 与mitmproxy-ca-cert.pem相同,只是改变了后缀,适用于部分Android平台
mitmproxy-dhparam.pem PEM格式的秘钥文件,用于增强SSL安全性

下面我们介绍一下Windows、Mac、iOS和Android平台下的证书配置过程。

首先配置电脑环境证书
Windows(如果电脑是win)

双击mitmproxy-ca.p12,就会出现导入证书的引导页,如图1-61所示。

图1-61 证书导入向导

直接点击“下一步”按钮即可,会出现密码设置提示,如图1-62所示。

图1-62 密码设置提示

这里不需要设置密码,直接点击“下一步”按钮即可。

接下来需要选择证书的存储区域,如图1-63所示。这里点击第二个选项“将所有的证书都放入下列存储”,然后点击“浏览”按钮,选择证书存储位置为“受信任的根证书颁发机构”,接着点击“确定”按钮,然后点击“下一步”按钮。

图1-63 选择证书存储区域

最后,如果有安全警告弹出,如图1-64所示,直接点击“是”按钮即可。

图1-64 安全警告

这样就在Windows下配置完CA证书了。

Mac(如果电脑是mac)

Mac下双击mitmproxy-ca-cert.pem即可弹出钥匙串管理页面,然后找到mitmproxy证书,打开其设置选项,选择“始终信任”即可,如图1-65所示。

图1-65 证书配置
然后配置手机端证书,Android/iPhone安装证书方法1(推荐)

在linux下启动 mitmproxy,命令为mitmproxy -p 8080,同时将手机代理设置为linux的IP地址与端口后访问mitm.it安装证书。

启动 mitmproxy
mitmweb    # mitmproxy有三种启动方式,此处使用的命令可以提供一个web交互界面

【提示】

mitmproxy有三种启动命令:

(1) mitmweb

– 提供一个web界面;

– 代理端口:绑定了 *:8080作为代理端口;

– 交互界面地址:localhost:8081;

(2) mitmproxy

– 提供命令行界面;

– 可以通过命令过滤请求;

(3) mitmdump

mitmdump 命令启动后,没有界面,程序默默运行,所以 mitmdump 无法提供过滤请求、查看数据的功能,只能结合自定义脚本,默默工作。

得到下图,表示启动成功,端口号默认为 8080。

mitmproxy服务器绑定了 *:8080作为代理端口

image

此时,浏览器自动打开下图页面,这是 mitmproxy 提供的 web 交互界面。

image

上图为mitmproxy自动打开的web交互界面

安装CA证书

第一步,将电脑和手机连到同一个 WiFi 中;

第二步,获取本机的内网IP地址;

ifconfig

得到电脑本机的内网IP地址为 192.168.1.102。

image

第三步,给手机WiFi配置代理

  • 服务器地址为电脑内网IP地址,端口为8080
image

第四步,安装证书

使用手机浏览器访问mitm.it,得到下图。

image

我的手机为iPhone,点击 Apple 后得到下图。

image

点击允许,开始安装。

image

安装完成后,得到已验证的提示。

image
开启证书

手机依次点击:设置 -> 通用 -> 关于本机 -> 证书信任设置,开启 mitmproxy 证书。

image

注意

  • 有小伙伴反馈,安卓 7.0 以上版本不再信任证书,需要将证书安装到 root 路径下,这导致数据抓包不成功、手机应用网络不通。
  • 博主还没有做过研究,想提示一下安卓 7.0 用户如果遇到这类问题,可以尝试使用模拟器降低安卓版本,看是否能解决问题,有时间的话麻烦把结果给博主反馈一下,谢谢!
配置完成

此时,mitmweb 页面出现下图内容,红框中的为 mitmproxy 抓取的手机的请求。

image

到这里,就成功完成了 mitmproxy配置。

然后配置手机端证书,Android/iPhone安装证书方法2
iOS(如果是IOS系统手机)

将mitmproxy-ca-cert.pem文件发送到iPhone上,推荐使用邮件方式发送,然后在iPhone上可以直接点击附件并识别安装,如图1-66所示。

图1-66 证书安装页面

点击“安装”按钮之后,会跳到安装描述文件的页面,点击“安装”按钮,此时会有警告提示,如图1-67所示。

图1-67 安装警告页面

继续点击右上角的“安装”按钮,安装成功之后会有已安装的提示,如图1-68所示。

图1-68 安装成功页面

如果你的iOS版本是10.3以下的话,此处信任CA证书的流程就已经完成了。

如果你的iOS版本是10.3及以上版本,还需要在“设置”→“通用”→“关于本机”→“证书信任设置”将mitmproxy的完全信任开关打开,如图1-69所示。此时,在iOS上配置信任CA证书的流程就结束了。

图1-69 证书信任设置
Android(手机系统是Android)

在Android手机上,同样需要将证书mitmproxy-ca-cert.pem文件发送到手机上,例如直接复制文件。

接下来,点击证书,便会出现一个提示窗口,如图1-70所示。

图1-70 证书安装页面

这时输入证书的名称,然后点击“确定”按钮即可完成安装。

简单使用

linux 下使用

mitmproxy过滤功能的使用举例:

输入z,清除屏幕上全部的包
输入f,进入编辑模式,可在最下面编辑条件,ESC或Enter退出编辑
!(~c 200) #显示所有返回不是200的请求
!(~c 200) & ~d baidu.com #显示域名包含baidu.com,返回不是200的请求
~m post & ~u baidu #显示请求的链接里面包含baidu的post请求
~d baidu.com (http://baidu.com) 过滤所有域名包含baidu.com (http://baidu.com)的包

mitmproxy断点功能的使用举例:

输入i,进入编辑模式,可在最下面编辑条件,ESC或Enter退出编辑
断点的条件和过滤是一样的,符合条件的链接会被拦截
~d baidu.com & ~m get
域名包含baidu.com的get 请求会被拦截
按Enter进入详情页,在详情页输入e进入模式,可以修改各项数据
完成后,回到请求显示列表,输入a,将请求放行.
1.请求重放
2.选中需要重放的请求,输入r可以重放请求,也可以先编辑后再重放
3.输入Q可退出程序
mitmproxy经常配合appium使用

编写一个 py 文件供 mitmproxy 加载,文件定义了变量 addons,addons 是个数组,每个元素是一个类实例,这些类有若干方法,这些方法实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的方法。这些类,称为一个个 addon,比如一个叫 Counter 的 addons:

  • addons.py
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()
]

这种使用会更方便也更容易管理和拓展。况且这也是官方内置的一些 addon 的实现方式。

启动 mitmproxy:

mitmweb --listen-port 8080 -s addons.py

当浏览器使用代理进行访问时,就应该能看到控制台里有类似这样的日志。

# mitmdump -p [port] -s [脚本文件]
$ mitmdump -q -s addons.py --set body-size-limit=10k "~m post" -w outfile
  • -q 屏蔽 mitmdump 默认的控制台日志,只显示自己脚本中的
  • -s 入口脚本文件
  • --set body-size-limit=10k 只处理小于 10k 的请求
  • “~m post” 只处理 post 方法的请求
  • -w 把截获的数据保存到文件中,其中outfile的名称任意,截获的数据都会被保存到此文件中
ctx模块

先导入ctx模块:from mitmproxy import ctx。这个模块主要用于日志的输出。日志输出可以控制颜色

ctx.log.info      # 日志输出颜色为白色
ctx.log.warn     # 日志输出颜色为黄色
ctx.log.error     # 日志输出颜色为红色
事件

上述的脚本估计不用我解释相信大家也看明白了,就是当 request 发生时,计数器加一,并打印日志。这里对应的是 request 事件

事件针对不同生命周期分为 5 类。“生命周期”这里指在哪一个层面看待事件,举例来说,同样是一次 web 请求,我可以理解为**“HTTP 请求 -> HTTP 响应”的过程,也可以理解为“TCP 连接 -> TCP 通信 -> TCP 断开”**的过程。那么,如果我想拒绝来个某个 IP 的客户端请求,应当注册函数到针对 TCP 生命周期 的 tcp_start 事件,又或者,我想阻断对某个特定域名的请求时,则应当注册函数到针对 HTTP 声明周期的 http_connect 事件。其他情况同理。

下面一段估计会又臭又长,如果你没有耐心看完,那至少看掉针对 HTTP 生命周期的事件,然后跳到下方的示例。

1. 针对 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 request(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 来自客户端的 HTTP 请求被成功完整读取。

def responseheaders(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 来自服务端的 HTTP 响应的头部被成功读取。此时 flow 中的 response 的 body 是空的。

def response(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 来自服务端端的 HTTP 响应被成功完整读取。

def error(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 发生了一个 HTTP 错误。比如无效的服务端响应、连接断开等。注意与“有效的 HTTP 错误返回”不是一回事,后者是一个正确的服务端响应,只是 HTTP code 表示错误而已。

2. 针对 TCP 生命周期
def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) 建立了一个 TCP 连接。

def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) TCP 连接收到了一条消息,最近一条消息存于 flow.messages[-1]。消息是可修改的。

def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) 发生了 TCP 错误。

def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):

(Called when) TCP 连接关闭。

3. 针对 Websocket 生命周期
def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 客户端试图建立一个 websocket 连接。可以通过控制 HTTP 头部中针对 websocket 的条目来改变握手行为。flow 的 request 属性保证是非空的的。

def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) 建立了一个 websocket 连接。

def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) 收到一条来自客户端或服务端的 websocket 消息。最近一条消息存于 flow.messages[-1]。消息是可修改的。目前有两种消息类型,对应 BINARY 类型的 frame 或 TEXT 类型的 frame。

def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) 发生了 websocket 错误。

def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):

(Called when) websocket 连接关闭。

4. 针对网络连接生命周期
def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):

(Called when) 客户端连接到了 mitmproxy。注意一条连接可能对应多个 HTTP 请求。

def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):

(Called when) 客户端断开了和 mitmproxy 的连接。

def serverconnect(self, conn: mitmproxy.connections.ServerConnection):

(Called when) mitmproxy 连接到了服务端。注意一条连接可能对应多个 HTTP 请求。

def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):

(Called when) mitmproxy 断开了和服务端的连接。

def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):

(Called when) 网络 layer 发生切换。你可以通过返回一个新的 layer 对象来改变将被使用的 layer。详见 layer 的定义。

5. 通用生命周期
def configure(self, updated: typing.Set[str]):

(Called when) 配置发生变化。updated 参数是一个类似集合的对象,包含了所有变化了的选项。在 mitmproxy 启动时,该事件也会触发,且 updated 包含所有选项。

def done(self):

(Called when) addon 关闭或被移除,又或者 mitmproxy 本身关闭。由于会先等事件循环终止后再触发该事件,所以这是一个 addon 可以看见的最后一个事件。由于此时 log 也已经关闭,所以此时调用 log 函数没有任何输出。

def load(self, entry: mitmproxy.addonmanager.Loader):

(Called when) addon 第一次加载时。entry 参数是一个 Loader 对象,包含有添加选项、命令的方法。这里是 addon 配置它自己的地方。

def log(self, entry: mitmproxy.log.LogEntry):

(Called when) 通过 mitmproxy.ctx.log 产生了一条新日志。小心不要在这个事件内打日志,否则会造成死循环。

def running(self):

(Called when) mitmproxy 完全启动并开始运行。此时,mitmproxy 已经绑定了端口,所有的 addon 都被加载了。

def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):

(Called when) 一个或多个 flow 对象被修改了,通常是来自一个不同的 addon。

示例

估计看了那么多的事件你已经晕了,正常,鬼才会记得那么多事件。事实上考虑到 mitmproxy 的实际使用场景,大多数情况下我们只会用到针对 HTTP 生命周期的几个事件。再精简一点,甚至只需要用到 http_connect、request、response 三个事件就能完成大多数需求了。

这里以一个稍微有点黑色幽默的例子,覆盖这三个事件,展示如果利用 mitmproxy 工作。

需求是这样的:

1、因为百度搜索是不靠谱的,所有当客户端发起百度搜索时,记录下用户的搜索词,再修改请求,将搜索词改为“360 搜索”;

2、因为 360 搜索还是不靠谱的,所有当客户端访问 360 搜索时,将页面中所有“搜索”字样改为“请使用谷歌”。

3、因为谷歌是个不存在的网站,所有就不要浪费时间去尝试连接服务端了,所有当发现客户端试图访问谷歌时,直接断开连接。

4、将上述功能组装成名为 Joker 的 addon,并保留之前展示名为 Counter 的 addon,都加载进 mitmproxy。

第一个需求需要篡改客户端请求,所以实现一个 request 事件:

def request(self, flow: mitmproxy.http.HTTPFlow):
    # 忽略非百度搜索地址
    if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):
        return
 
    # 确认请求参数中有搜索词
    if "wd" not in flow.request.query.keys():
        ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)
        return
 
    # 输出原始的搜索词
    ctx.log.info("catch search word: %s" % flow.request.query.get("wd"))
    # 替换搜索词为“360搜索”
    flow.request.query.set_all("wd", ["360搜索"])

第二个需求需要篡改服务端响应,所以实现一个 response 事件:

def response(self, flow: mitmproxy.http.HTTPFlow):
    # 忽略非 360 搜索地址
    if flow.request.host != "www.so.com":
        return
 
    # 将响应中所有“搜索”替换为“请使用谷歌”
    text = flow.response.get_text()
    text = text.replace("搜索", "请使用谷歌")
    flow.response.set_text(text)

第三个需求需要拒绝客户端请求,所以实现一个 http_connect 事件:

def http_connect(self, flow: mitmproxy.http.HTTPFlow):
    # 确认客户端是想访问 www.google.com
    if flow.request.host == "www.google.com":
        # 返回一个非 2xx 响应断开连接
        flow.response = http.HTTPResponse.make(404)

为了实现第四个需求,我们需要将代码整理一下,即易于管理也易于查看。

import mitmproxy.http
from mitmproxy import ctx, http
 
 
class Joker:
    def request(self, flow: mitmproxy.http.HTTPFlow):
        if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):
            return
 
        if "wd" not in flow.request.query.keys():
            ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)
            return
 
        ctx.log.info("catch search word: %s" % flow.request.query.get("wd"))
        flow.request.query.set_all("wd", ["360搜索"])
 
    def response(self, flow: mitmproxy.http.HTTPFlow):
        if flow.request.host != "www.so.com":
            return
 
        text = flow.response.get_text()
        text = text.replace("搜索", "请使用谷歌")
        flow.response.set_text(text)
 
    def http_connect(self, flow: mitmproxy.http.HTTPFlow):
        if flow.request.host == "www.google.com":
            flow.response = http.HTTPResponse.make(404)

创建一个 counter.py 文件,内容为:

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.py 文件,内容为:

import counter
import joker
 
addons = [
    counter.Counter(),
    joker.Joker(),
]

将三个文件放在相同的文件夹,在该文件夹内启动命令行,运行:

mitmweb -s addons.py
城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐