Mitmproxy + Python实现http/https拦截(中间人攻击)

我们在分析http请求时一般时使用Charles或其他类似的代理工具,这类工具虽然在分析上有着比较大时优势,在修改上也有一定的支持,不过在实际使用中会有些困难,你可能需要一些有着更大自由度的工具,用来修改报文、固化攻击策略以及在团队协作上的标准交互(口头描述什么的太。。。)。所以这里讲介绍如何使用Mitmproxy(Man-in-the-middle proxy)进行http的拦截/篡改。

安装

运行环境

  • Python 3.6 + pip及以上版本
    Python的安装请自行百度,这里就不展开 Download Python | Python.org

  • Python编程的基本知识

安装Mitmproxy

Mitmproxy是Python上的一个工具包(当然也有打包后的,但是自由度就没了),安装完Python3后只要一条命令就可以了:

pip3 install mitmproxy

安装完成后你将拥有mitmproxy(Linux)、mitmdump、mitmweb 三条命令

使用

命令

三个命令实际功能都差不多,只是展示的形式不太一样,大家可以选择性的使用。

  • mitmproxy
    这个是Linux上才支持的命令,可以实时查看http请求情况。是一个命令行的窗口形式。
  • mitmweb
    提供一个web页面,可以实时查看http请求情况。
  • mitmdump
    纯后台的一种状态。

脚本

脚本就是Mitmproxy的精华了,Mitmproxy可以有两种形式的脚本:一个是直接在Python文件中实现指定的方法,比如request()、response();还有一个是将这些方法放到一个类中,作为一个addon加入到addons里面,下面我们只介绍这一种(第一种只是第二种的简化版)。

最小例子

import mitmproxy.http
from mitmproxy import ctx

class Interceptor:
    def __init__(self):
        ctx.log.info("init")

    def request(self, flow: mitmproxy.http.HTTPFlow):
        ctx.log.info("request() called")

    def response(self, flow: mitmproxy.http.HTTPFlow):
        ctx.log.info("response() called")


addons = [
    Interceptor()
]

将上面这部分存为interceptor.py,将系统代理设置到本机即可
注:我这里指定里代理端口为8888,可根据需要自行配置(默认为8080)

Windows10代理设置

执行以下命令,即可看到浏览信息

mitmweb  --listen-port 8888 --web-port 8848 -s interceptor.py
拦截记录

Log记录
  • https证书问题
    访问https地址时你可能会发现浏览器不能正常显示了(302错误),这是因为你还没有安装Mitmproxy的CA证书。
    启动代理后,打开http://mitm.it/,不出意外你会看到以下这个页面,如果没有请检查你的代理有没有设置正常。
    CA证书下载

事件

当我们执行一次http请求时实际时发生很多事件的,并非上面写的只有request()和response()(不过一般用这两个也就够了),以下是Mitmproxy定义的一系列事件,我们可以在每个事件中做操作,其中http的事件比较常用,这里大概了解即可,用到再查也不迟。
官方文档-Events

HTTP事件

  • def http_connect(self, flow: mitmproxy.http.HTTPFlow)
    收到了来自客户端的 HTTP CONNECT 请求。在 flow 上设置非 2xx 响应将返回该响应并断开连接。CONNECT 不是常用的 HTTP 请求方法,目的是与服务器建立代理连接,仅是 client 与 proxy 的之间的交流,所以 CONNECT 请求不会触发 request、response 等其他常规的 HTTP 事件。
  • def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
    来自客户端的 HTTP 请求的头部被成功读取。此时 flow 中的 request 的 body 是空的。
  • def request(self, flow: mitmproxy.http.HTTPFlow):
    来自客户端的 HTTP 请求被成功完整读取。
  • def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
    来自服务端的 HTTP 响应的头部被成功读取。此时 flow 中的 response 的 body 是空的。
  • def response(self, flow: mitmproxy.http.HTTPFlow):
    来自服务端端的 HTTP 响应被成功完整读取。
  • def error(self, flow: mitmproxy.http.HTTPFlow):
    发生了一个 HTTP 错误。比如无效的服务端响应、连接断开等。注意与“有效的 HTTP 错误返回”不是一回事,后者是一个正确的服务端响应,只是 HTTP code 表示错误而已。

通用事件

  • def configure(self, updated: typing.Set[str]):
    配置发生变化。updated 参数是一个类似集合的对象,包含了所有变化了的选项。在 mitmproxy 启动时,该事件也会触发,且 updated 包含所有选项。
  • def done(self):
    addon 关闭或被移除,又或者 mitmproxy 本身关闭。由于会先等事件循环终止后再触发该事件,所以这是一个 addon 可以看见的最后一个事件。由于此时 log 也已经关闭,所以此时调用 log 函数没有任何输出。
  • def load(self, entry: mitmproxy.addonmanager.Loader):
    addon 第一次加载时。entry 参数是一个 Loader 对象,包含有添加选项、命令的方法。这里是 addon 配置它自己的地方。
  • def log(self, entry: mitmproxy.log.LogEntry):
    通过 mitmproxy.ctx.log 产生了一条新日志。小心不要在这个事件内打日志,否则会造成死循环。
  • def running(self):
    mitmproxy 完全启动并开始运行。此时,mitmproxy 已经绑定了端口,所有的 addon 都被加载了。
  • def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):
    一个或多个 flow 对象被修改了,通常是来自一个不同的 addon。

TCP事件

  • def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
    建立了一个 TCP 连接。
  • def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):
    TCP 连接收到了一条消息,最近一条消息存于 flow.messages[-1]。消息是可修改的。
  • def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
    发生了 TCP 错误。
  • def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
    TCP 连接关闭。

Websocket事件

  • def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):
    客户端试图建立一个 websocket 连接。可以通过控制 HTTP 头部中针对 websocket 的条目来改变握手行为。flow 的 request 属性保证是非空的的。
  • def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):
    建立了一个 websocket 连接。
  • def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
    收到一条来自客户端或服务端的 websocket 消息。最近一条消息存于 flow.messages[-1]。消息是可修改的。目前有两种消息类型,对应 BINARY 类型的 frame 或 TEXT 类型的 frame。
  • def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):
    发生了 websocket 错误。
  • def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):
    websocket 连接关闭。

网络连接事件

  • def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):
    客户端连接到了 mitmproxy。注意一条连接可能对应多个 HTTP 请求。
  • def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):
    客户端断开了和 mitmproxy 的连接。
  • def serverconnect(self, conn: mitmproxy.connections.ServerConnection):
    mitmproxy 连接到了服务端。注意一条连接可能对应多个 HTTP 请求。
  • def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):
    mitmproxy 断开了和服务端的连接。
  • def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):
    网络 layer 发生切换。你可以通过返回一个新的 layer 对象来改变将被使用的 layer。

事例

没有什么事情不是来一个例子不能解决的

import mitmproxy.http
from mitmproxy import ctx
from mitmproxy import flowfilter

class Interceptor:
    def __init__(self):
        # 添加网址过滤器
        self.filter = flowfilter.parse("~u https://www.baidu.com")

    def request(self, flow: mitmproxy.http.HTTPFlow):
        if flowfilter.match(self.filter, flow):  
            ctx.log.info("match request")
            # 替换搜索词
            flow.request.query["wd"] = "富婆通讯录"

    def response(self, flow: mitmproxy.http.HTTPFlow):
        if flowfilter.match(self.filter, flow):  
            ctx.log.info("match response")
            # 添加/修改headers
            flow.response.headers["md5"] = "00112233445566778899AABBCCDDEEFF"
            flow.response.content = bytes("想啥呢",encoding='utf8')

addons = [
    Interceptor()
]

再执行一下这个例子,访问百度看看?删掉response()部分再试试?
注:Mitmproxy支持热更新,修改脚本保存后悔自动加载,不用重新启动。

浏览器F12,你懂的

这里只是简单的一个例子,更多例子可以参考官方的例子。
Example Addons (mitmproxy.org)

错误

  • 执行命令时你可能会遇到这样的错误:
    启动错误

    这个一般是因为端口被占用里,指定一个空闲的端口即可
mitmweb --listen-port 8888 --web-port 8848
  • 部分https不能正常请求
    有些https有做证书校验,即使加了Mitmproxy的CA,由于证书不同也会被拒绝,可以在web页面中配置allow_hosts选项,忽略部分请求。

参考文献

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容