美文网首页
Mitmproxy + Python实现http/https拦截

Mitmproxy + Python实现http/https拦截

作者: 云华兄 | 来源:发表于2021-04-28 17:48 被阅读0次

    我们在分析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选项,忽略部分请求。

    参考文献

    相关文章

      网友评论

          本文标题:Mitmproxy + Python实现http/https拦截

          本文链接:https://www.haomeiwen.com/subject/yhxvrltx.html