• 【爬虫与逆向】玩转强大的mitmproxy


    认识mitmproxy

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

    安装

    一条命令即可:

    pip install mitmproxy

    但要注意和pyOpenSSL版本的兼容性,否则运行不起来。目前mitmproxy的版本为9.0.1,对应的pyOpenSSL需要22.1.0的版本。
    接下来重要的一步是安装mitmproxy的证书,毕竟目前大部分网站都是采用https协议的。
    ~/.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)
            
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在自己的程序中运行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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    #!/usr/bin/env python
    from mitmproxy.tools.main import mitmproxy
    mitmproxy()
    
    
    • 1
    • 2
    • 3
    • 4

    第二种方式更为方便:

    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())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    大家看到,这里我们很容易添加自己的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()]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    修改请求参数

    """Modify HTTP query parameters."""
    from mitmproxy import http
    
    
    def request(flow: http.HTTPFlow) -> None:
        flow.request.query["mitmproxy"] = "rocks"
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    重定向请求

    """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"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    修改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")]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    修改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
    
    • 1
    • 2
    • 3
    • 4

    发送新的请求

    有时我们要在请求流中加入自己的请求,可以借助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": "", "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])
    
    • 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
    • 31
    • 32

    编写 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)
    
    • 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

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    addons.py:

    import counter
    import joker
    
    addons = [
        counter.Counter(),
        joker.Joker(),
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行:

    mitmweb -s addons.py
    
    • 1

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

    其它类似软件

    更广泛一点的抓包/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
  • 相关阅读:
    Python:实现random forest regressor随机森林回归器算法(附完整源码)
    奥地利博士联培申请签证经验(奥地利签证)
    单细胞多模态GAN揭示三阴性乳腺癌的空间模式
    Python办公自动化之Word
    Could not autowire field: protected tk.mybatis.mapper.common.Mapper
    力扣刷题-删除链表中的重复节点
    【附源码】计算机毕业设计SSM手机测试管理系统
    springboot实现支付宝支付功能
    Nat. Biomed. Eng.| 综述:医学和医疗保健中的自监督学习
    裸辞18K外包,面试阿里、字节全都一面挂,哭死.....
  • 原文地址:https://blog.csdn.net/jgku/article/details/127699465