【高级篇】两个mitmproxy实例级联轻松调试抓包过程

认识mitmproxy

上半年搞过一段时间的mitmproxy,今天为了研究如何涨粉,又一次拾起这个利器。
mitmproxy能干什么?除了能抓包,还能修改请求和响应数据。最重要的一点是可编程性,让你通过python 操控HTTP请求。

安装

一条命令即可:

pip install mitmproxy

但要注意和pyOpenSSL版本的兼容性,否则运行不起来。目前mitmproxy的版本为9.0.1,对应的pyOpenSSL需要22.1.0的版本。
接下来重要的一步是安装mitmproxy的证书,毕竟目前大部分网站都是采用https协议的。
~/.mitmproxy下存放着几个证书,同时安装到系统中和浏览器中即可。

安装mitmproxy证书

安装到操作系统,不同平台各有安装方式。在ubuntu下,先将~/.mitmproxy/mitmproxy-ca-cert.crt拷到/usr/local/share/ca-certificates/,然后执行:

sudo update-ca-certificates

如果不安装证书,使用mimproxy作为chrome代理的话,会报错:
在这里插入图片描述

安装到浏览器中,以chrome为例:
在这里插入图片描述打开设置,导入后,将mitmproxy证书设为信任:

在这里插入图片描述

如何运行?

mitmproxy提供了三种运行方式,五种代理模式。

  • mitmproxy
  • mitmdump
  • mitmweb
    三种方式都在命令行运行,mitmweb提供一个wen界面,能直观观察到HTTP数据。mitmweb能拦截请求,让你修改请求数据,然后再发送,但这一功能有时不实用,你数据修改完,请求有时超时了,所以还得靠脚本。这种可以被mitmproxy加载的脚本叫addon,命令行通过-s参数指定。
    理想方式是在IDE里直接运行mitmproxy,然后方便调试它。官网并没有说明如何在自己程序里运行mitmproxy,笔者经过查看源码,找到了合理的方式。

把mitmproxy作为代理服务器

这是最基本的使用场景,可以给浏览器或者自己的程序设置代理,指向mitmproxy。
先启动mitmproxy:

mitmweb -p 8080 --ssl-insecure

然后在自己的程序中设置代理:

def follow_user(self, user_id):
        # e0ffdf6ea1e5446e841cb18deda609b9
        user_token = 'e0DDdf6ea1e5DD41cb18deda60Xse'
        self.session.cookies.update({'UserToken': user_token})
        ip = 'localhost:8080'
        proxy = {'http': f'http://{ip}', 'https': f'http://{ip}'}
        data = {"username":"xxx","follow":user_id,"source":"ME","fromType":"pc","sourceName":"主页"}
        reponse = self.session.post('https://mp-action.cdns.net/intreact/wrapper/pc/fanes/v1/api/yollow', proxies=proxy, headers = HEADERS, json = data, verify = False)
        

在自己的程序中运行mitmproxy

第一种方式:

from mitmproxy.tools.main import mitmdump, run
from mitmproxy.tools import dump
from mitmproxy.tools import cmdline

def main():
    args = ['-p', '1080', '-m', 'socks5', '--listen-host', '0.0.0.0']
    def extra(args):
        if args.filter_args:
            v = " ".join(args.filter_args)
            return dict(
                save_stream_filter=v,
                readfile_filter=v,
                dumper_filter=v,
            )
        return {}

    run(dump.DumpMaster, cmdline.mitmdump, args, extra)

这种方式其实等价于命令行执行方式。注意, 你无法直接采用下面这种方式运行:

#!/usr/bin/env python
from mitmproxy.tools.main import mitmproxy
mitmproxy()

第二种方式更为方便:

def main():
    async def _main():
        # options = main.options.Options(listen_host='0.0.0.0', listen_port=1080, mode=['socks5'])
        options = main.options.Options(listen_host='0.0.0.0', listen_port=1080, mode=['upstream:https://localhost:8080'], ssl_insecure=True)

        m = DumpMaster(options=options)

        m.server = Proxyserver()
        m.addons.add(Addon())
        # m.addons.add(FlowRecorderAddon())
        # m.addons.add(JSONDumper())
        # m.addons.add(DBDumper())
        await m.run()

    asyncio.run(_main())

大家看到,这里我们很容易添加自己的addon,这样就非常方便做各种调试了。

实战

有了上述准备,我们可以找一个网站的POST请求,修改一下post数据和cookie,这样就能做些自己的事情了,千万别拿这招去干坏事啊。
此案例中,我们采用两个mitmproxy级联的方式,其实一个也够用,采用两个的好处是,一个接收请求和处理请求,另一个方便观察我们发送的数据是否符合预期,当然你如果用浏览器的devtool也完全可以做到。
upstream模式图例
其中一个运行在我们的应用中,采用upstream模式,另一个就是普通的regular模式。浏览器的请求先发送到upstream proxy,然后再转发到第二个mitmproxy,最后出去到外网。

首先,在浏览器里装上SwitchyOmega,这个是搞web开发人必备的插件。
在这里插入图片描述
然后,在命令行启动第二个mitproxy:

mitmweb -p 8080 --ssl-insecure

最后,在IDE里启动upstream proxy,代码见前面。在浏览器里访问网站页面后,我们在mitmweb界面里就能见到请求了:
在这里插入图片描述
你可以在addon里查看POST的json数据,和各种cookie,如何修改它们?这里举几个常用的例子:

修改请求头

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()]

修改请求参数

"""Modify HTTP query parameters."""
from mitmproxy import http


def request(flow: http.HTTPFlow) -> None:
    flow.request.query["mitmproxy"] = "rocks"

重定向请求

"""Redirect HTTP requests to another server."""
from mitmproxy import http


def request(flow: http.HTTPFlow) -> None:
    # pretty_host takes the "Host" header of the request into account,
    # which is useful in transparent mode where we usually only have the IP
    # otherwise.
    if flow.request.pretty_host == "example.org":
        flow.request.host = "mitmproxy.org"

修改form表单数据

"""Modify an HTTP form submission."""
from mitmproxy import http


def request(flow: http.HTTPFlow) -> None:
    if flow.request.urlencoded_form:
        # If there's already a form, one can just add items to the dict:
        flow.request.urlencoded_form["mitmproxy"] = "rocks"
    else:
        # One can also just pass new form data.
        # This sets the proper content type and overrides the body.
        flow.request.urlencoded_form = [("foo", "bar")]

修改cookie

def request(self, flow):
	all_cookies = parse_cookies(flow.request.cookies) # 将string转成dict
    all_cookies.append({"name":"key1", "value":"value1"}) ## 添加一个cookie
	flow.request.headers["cookie"] = stringify_cookies(all_cookies) # dict转回string

发送新的请求

有时我们要在请求流中加入自己的请求,可以借助clientplayback这个addon。

from copy import copy
from mitmproxy import http
from mitmproxy import ctx
from mitmproxy.addons import clientplayback


def request(flow: http.HTTPFlow):
    ctx.log.info("Inside request")

    if hasattr(flow.request, 'is_custom'):
        return

    headers = copy(flow.request.headers)
    headers.update({"Authorization": "<removed>", "Requested-URI": flow.request.pretty_url})

    req = http.HTTPRequest(
        first_line_format="origin_form",
        scheme='http',
        port=8000,
        path="/",
        http_version=flow.request.http_version,
        content=flow.request.content,
        host="localhost",
        headers=headers,
        method=flow.request.method
    )

    req.is_custom = True
    playback = ctx.master.addons.get('clientplayback')
    f = flow.copy()
    f.request = req
    playback.start_replay([f])

编写 addons

mitmproxy提供修改request/response的标准方式为addons,下面写了两个addon,然后运行mitmweb加载。

joker.py:

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

如有其它需求,也可以联系我。

其它类似软件

更广泛一点的抓包/web debugger软件有:

  • Fiddler
  • Burp Suite
  • Charles
  • HTTP Toolkit
  • Proxyman

HTTP抓包和诊断工具

  • https://github.com/inspectdev/inspect-release/releases
  • https://github.com/auchenberg/chrome-devtools-app
  • https://github.com/ChromeDevTools/awesome-chrome-devtools
  • https://www.npmjs.com/package/chrome-devtools-frontend
  • https://alternativeto.net/software/mitmproxy/
  • https://www.httpdebugger.com/
  • https://github.com/snail007/goproxy
  • golang版mitmproxy
  • node版mitmproxy
  • Java版mitmproxy
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北极象

如果觉得对您有帮助,鼓励一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值