mitmproxy

本文最后更新于:2020年9月27日 晚上

简介

顾名思义,mitmproxy 就是用于 MITM 的 proxy,MITM 即中间人攻击(Man-in-the-middle attack)。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为

不同于 fiddler 或 wireshark 等抓包工具,mitmproxy 不仅可以截获请求帮助开发者查看、分析,更可以通过自定义脚本进行二次开发。举例来说,利用 fiddler 可以过滤出浏览器对某个特定 url 的请求,并查看、分析其数据,但实现不了高度定制化的需求,类似于:“截获对浏览器对该 url 的请求,将返回内容置空,并将真实的返回内容存到某个数据库,出现异常时发出邮件通知”。而对于 mitmproxy,这样的需求可以通过载入自定义 python 脚本轻松实现

mitmproxy 工作在 HTTP 层, HTTPS 的普及让客户端拥有了检测并规避中间人攻击的能力,所以要让 mitmproxy 能够正常工作,必须要让客户端(APP 或浏览器)主动信任 mitmproxy 的 SSL 证书,或忽略证书异常

利用手机模拟器、无头浏览器来爬取 APP 或网站的数据,mitmpproxy 作为代理可以拦截、存储爬虫获取到的数据,或修改数据调整爬虫的行为

文档

信息 链接
官网 https://mitmproxy.org/
文档 https://docs.mitmproxy.org/stable/
Github https://github.com/mitmproxy/mitmproxy
DockerHub https://hub.docker.com/r/mitmproxy/mitmproxy/

安装

安装程序


官网首页将安装包下载下来,选择一个地方安装好

安装证书

Windows

在用户目录下找到mitmproxy证书进行安装
一般会在C:\Users\Administrator\.mitmproxy文件夹中
证书名为mitmproxy-ca.p12,双击开始安装

一路下一步,到这里,选择受信任的根证书颁发机构
最后会弹出警告,选是即可

Android

操作与上述似,但证书文件是mitmproxy-ca-cert.pem

python库安装

1
pip install mitmproxy

使用代理以验证安装

cmd中输入mitmweb以启用mitmweb

启用后会提示 web运行信息 和 代理信息
将代理信息记录下来,设置到 浏览器代理 或者 系统代理之中

若是使用Chrome浏览器,推荐使用插件SwitchyOmega来管理代理
https://github.com/FelisCatus/SwitchyOmega

进行设置以后,请求/响应都会通过代理进行,在不使用代理时注意关闭

最终会抓到一些包,显示在控制台上

插件 Addons

MitmproxyAddons机制由一组API组成
Addons通过响应事件与mitmproxy进行交互,从而使它们能够参与并改变mitmproxy的行为
Addons是通过Option配置的,这些选项可以在mitmproxy的配置文件中设置,也可以由用户交互地更改或通过命令行进行修改
Addonsmitmproxy的重要组成部分,实际上很多mitmproxy自身的函数都是被定义在 一套Addons 中的
Mitmproxy为第三方脚本编写程序和扩展程序提供了一套完全相同的工具来实现它自己的功能

1
pydoc mitmproxy.http

你可以通过这个命令来查阅API文档

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"""
一个简单的 mitmproxy addon.
你可以通过这个命令运行: mitmproxy -s anatomy.py
"""
from mitmproxy import ctx
class Counter:
def __init__(self):
self.num = 0

def request(self, flow):
self.num = self.num + 1
ctx.log.info("We've seen %d flows" % self.num)

addons = [
Counter()
]

这是个简单的案例,每当发送新的请求,都会在控制台输出文字

事件 Events

Addons通过Events与mitmproxy的内部机制挂钩
许多Events接收Flow对象作为参数,通过修改这些参数,Addons能改变包的信息

案例

1
2
3
4
5
6
7
8
9
10
11
12
"""例子:为每一个响应添加一个统计数量的响应头"""
class AddHeader:
def __init__(self):
self.num = 0

def response(self, flow):
self.num = self.num + 1
flow.response.headers["count"] = str(self.num)

addons = [
AddHeader()
]

所支持的所有Eventshttps://docs.mitmproxy.org/stable/addons-events/

选项 Options

Mitmproxy核心的全局设置存储,包含了决定Mitmproxy及其插件行为的设置
Options会从配置文件中读取,你可以通过命令行来动态改变它

所有Options都使用一组受支持的类型进行。Mitmproxy知道如何序列化和反序列化这些类型。强行设置不合适的值会导致错误

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"""
添加一个新的 mitmproxy option
启动命令: mitmproxy -s 4_options.py --set addheader true
"""
from mitmproxy import ctx
class AddHeader:
def __init__(self):
self.num = 0

def load(self, loader):
loader.add_option(
name = "addheader",
typespec = bool,
default = False,
help = "Add a count header to responses",
)

def response(self, flow):
if ctx.options.addheader:
self.num = self.num + 1
flow.response.headers["count"] = str(self.num)
addons = [
AddHeader()
]

load Event会接收一个可以声明Option和命令的mitmproxy.addonmanager.Loader对象。这个Addon添加了一个bool类型的addheader Option
在命令行里运行脚本

1
mitmproxy -s 4_options.py --set addheader true

通过代理访问一下网站,就能够在命令行看到一些信息
如果你观察包,你会发现HTTP headers并没有addheader项。这是因为add_option的默认值为false它并不会启动
O进入options编辑器,找到addheader项并将它改为true,你就会看到添加了计数项的响应头部

1
2
3
4
HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Length: 219
count: 1

在加载这个Addon的时候,addheader的设置会保存在 设置文件YAML
你也可以在运行命令的时候用--set参数来设置options

1
mitmproxy -s ./examples/addons/options-simple.py --set addheader=true

配置更新

configure event能在option更变的时候立即做一些响应
option发生变更,configure event 会被被触发。它会收到一组被更变的optionssetAddon可以检查option是否在set里,然后根据上下文的option对象里获取值并作出一些响应
这功能最常见的用法是用来检查option是否有效,若无效,则进行记录(反馈)

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"""React to configuration changes."""
import typing
from mitmproxy import ctx
from mitmproxy import exceptions
class AddHeader:
def load(self, loader):
loader.add_option(
name = "addheader",
typespec = typing.Optional[int],
default = None,
help = "Add a header to responses",
)

def configure(self, updates):
if "addheader" in updates:
if ctx.options.addheader is not None and ctx.options.addheader > 100:
raise exceptions.OptionsError("addheader must be <= 100")

def response(self, flow):
if ctx.options.addheader is not None:
flow.response.headers["addheader"] = str(ctx.options.addheader)


addons = [
AddHeader()
]

在你设置一个不符合规定的addheader值时,configure event会给你反馈信息

1
2
3
mitmdump -s ./examples/addons/options-configure.py --set addheader=1000
Loading script: ./examples/addons/options-configure.py
/Users/cortesi/mitmproxy/mitmproxy/venv/bin/mitmdump: addheader must be <= 100

options支持的数据类型

str, int, float, bool, typing.Optional, typing.Sequence

命令 Commands

Commands允许用户与插件进行主动交互-查询其状态,命令他们执行操作以或者让它们转换数据。
Commands是一个非常强大的结构,mitmproxy控制台中的所有用户交互都是通过将命令绑定到keys来构建的

案例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"""添加一个用户自定义Commands"""
from mitmproxy import command
from mitmproxy import ctx


class MyAddon:
def __init__(self):
self.num = 0

@command.command("myaddon.inc")
def inc(self) -> None:
self.num += 1
ctx.log.info(f"num = {self.num}")


addons = [
MyAddon()
]

运行脚本代码

1
mitmproxy -s commands-simple.py

输入自己设置的命令即可得到结果

1
:myaddon.inc

注意:

  • 命令应该是唯一的,不要弄重复
  • 你只能运用那些option里能用的变量类型(包括返回值)

案例2 与flows配合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import typing
from mitmproxy import command
from mitmproxy import ctx
from mitmproxy import flow
class MyAddon:
@command.command("myaddon.addheader")
def addheader(self, flows: typing.Sequence[flow.Flow]) -> None:
for f in flows:
f.request.headers["myheader"] = "value"
ctx.log.alert("done")

addons = [
MyAddon()
]

myaddon.addheader命令十分简单,它获取一系列的flows被为请求添加一个头部参数
这个案例真正有趣的地方是用户如何指定flowsmitmproxy可以检查类型信息,这意味着你能通过类型信息来设置一些过滤条件,增加灵活性
启动脚本

1
mitmproxy -s ./examples/addons/commands-flows.py

尝试不同的参数
只对@focusflows执行addheader

1
:myaddon.addheader @focus

对所有的flows执行addheader

1
:myaddon.addheader @all

只对来自 google.com 的flows执行addheader

1
:myaddon.addheader ~d google.com

案例3 路径类型 paths

命令可以带有任意数量的参数,演示一种特殊类型:paths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"""将文件路径作为命令参数处理"""
import typing

from mitmproxy import command
from mitmproxy import ctx
from mitmproxy import flow
from mitmproxy import types


class MyAddon:
@command.command("myaddon.histogram")
def histogram(
self,
flows: typing.Sequence[flow.Flow],
path: types.Path,
) -> None:
totals = {}
for f in flows:
totals[f.request.host] = totals.setdefault(f.request.host, 0) + 1

with open(path, "w+") as fp:
for cnt, dom in sorted([(v, k) for (k, v) in totals.items()]):
fp.write("%s: %s\n" % (cnt, dom))

ctx.log.alert("done")


addons = [
MyAddon()
]

命令的路径参数会作为第二参数来传入,你可以用类似于这种方式来使用它

1
:myaddon.histogram @all /tmp/xxx

支持的类型

  • Primitive types: str, int, bool
  • Sequences: typing.Sequence[str]
  • Flows and flow sequences: flow.Flow and typing.Sequence[flow.Flow]
  • Multiple choice strings: types.Choice
  • Meta-types: types.Command and types.Arg. These are for constructing commands that invoke other commands. This is most commonly useful in keybinding - see the built-in mitmproxy console keybindings for a rich suite of examples
  • Data types: types.CutSpec and types.Data. The cuts mechanism is in alpha at the moment, and provides a convenient way to snip up flow data
  • Path: types.Path

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!