mitmproxy入门三、直接python脚本运行&常用http处理方式

在mitmproxy入门第一篇中讲到通过类似“mitmdump -q -s main.py”这种命令可以实现脚本工作,其实也可以直接以运行python脚本的方式启动。本文为mintmproxy 版本10、python3.10

一、直接运行python文件启动

主入口参考文件main.py

import mitmproxy.http
from mitmproxy import ctx, http

#import threading
import asyncio
from mitmproxy.options import Options
from mitmproxy.tools import main
from mitmproxy.tools import dump

import action1
import action2

#这里的addons 是给mitmdump -q -s main.py命令执行方式用的
addons = [
    action1.Action1(),
    action2.Action2()
]

if __name__ == '__main__':
    #from mitmproxy.tools.dump import DumpMaster
    async def func_temp(host,port):
        opts = Options(listen_host=host, listen_port=port)
        #with_termlog设置为True会显示运行中mitmproxy遇到的错误;with_dumper是用来控制是否显示每个请求相关信息的
        dm = dump.DumpMaster(opts, with_termlog=True, with_dumper=False)
        # python脚本直接运行需要按如下格式一个一个add添加
        dm.addons.add(action1.Action1())
        dm.addons.add(action2.Action2())
        #print(dm.addons)
        #dm.addons.add([action1.Action1(),action2.Action2()])#这种语法不行
        #dm.addons = [
        #   action1.Action1(),
        #    action2.Action2()
        #]#这种也不行

        try:
            await dm.run()
        except BaseException as e:
            print(e)
            dm.shutdown() 
    asyncio.run(func_temp('127.0.0.1', 8080))    

参考action1.py

import mitmproxy.http
from mitmproxy import ctx, http

class Action1:
    def request(self, flow: mitmproxy.http.HTTPFlow):
        print("这里是action11----------")
        print(flow.request.host)
        #t = 1/0
        #flow.response = http.Response.make(200,"111",)
        return

    def response(self, flow):
        #t =1/0
        #flow.response = http.Response.make(200,"111+111",)
        return

参考action2.py

import mitmproxy.http
from mitmproxy import ctx, http

class Action2:
    def request(self, flow: mitmproxy.http.HTTPFlow):
        print("这里是action22----------")
        print(flow.request.host)
        #t = 1/0
        #flow.response = http.Response.make(200,"222",)
        return

    def response(self, flow):
        #t =1/0
        #flow.response = http.Response.make(200,"222+222",)
        return

上图蓝色框的是如果要直接在python脚本中运行所需添加的内容格式。

二、常用的操作http请求的示例

直接在上面action1.py里面做下修改,可以方便地进行测试(测试代码有点乱)

import mitmproxy.http
from mitmproxy import ctx, http
import time
class Action1:
    def request(self, flow: mitmproxy.http.HTTPFlow):
        #flow.request.host='www.google.com'
        #t=1/0
        #print("请求将被kill,后续将不会有response")
        #flow.kill()

        if 'action' in flow.request.url:
            print("触发了拦截,会阻塞后面的请求,即使请求不进入本逻辑")
            flow.intercept()
            a = input("输入1 放行: ")
            if a=='1':
                #time.sleep(20)
                flow.resume()#继续流动 - 在一个intercept()之后调用
            else:
                print("输入不是1,此请求不再放行")
        else:
            print("不拦截")

        #pretty_host #类似于host,但使用主机头作为附加的首选数据源。这在透明模式下很有用,host只有IP地址,但可能不会反映实际的目的地,因为主机头可能被欺骗。
        flow.request.method #请求方式。POST、GET等
        flow.request.scheme #什么请求 ,应为“http”或“https”
        #flow.response = http.Response.make(200,"111",)
        flow.request.query #返回MultiDictView类型的数据,url直接带的键值参数
        flow.request.query.keys()#取得所有请求参数(不包含参数对应的值)
        print(list(flow.request.query.keys()))
        print(" ")
        act1 =flow.request.query.get('action')#取得请求参数wd的值
        print(f"act1={act1}")

        #flow.request.query.set_all(key,[value])#修改请求参数
        flow.request.query.set_all('action',['edit-action'])
        act2 =flow.request.query.get('action')
        print(f"act2={act2}")

        flow.request.cookies["log_id"] = "007"#修改cookie
        flow.request.get_content()#bytes,结果如flow.request.get_text()
        flow.request.raw_content #bytes,结果如flow.request.get_content()
        flow.request.urlencoded_form #MultiDictView,content-type:application/x-www-form-urlencoded时的请求参数,不包含url直接带的键值参数
        flow.request.urlencoded_form["code"] = "123456"#修改或赋值
        flow.request.urlencoded_form = [("code", "123456"),("name","lucy")]
        flow.request.multipart_form #MultiDictView,content-type:multipart/form-data
        return

    def response(self, flow):
        #t =1/0
        #flow.response = http.Response.make(200,"111+111",)
        #flow.response = flow.response.make(404)#返回404
        #print(flow.response.headers)
        for (k,v) in flow.response.headers.items():
            print(f"{k}:{v}")
        print(" ")
        #print(flow.response.get_text())
        flow.response.status_code #状态码
        flow.response.text#返回内容,已解码
        #print(flow.response.text)#返回内容,已解码
        flow.response.content #返回内容,二进制
        #flow.response.setText()#修改返回内容,不需要转码
        flow.response.set_text(flow.response.get_text().replace('<title>', '<title>返回title——'))
        flow.response.headers["isMitmproxy"]='yes'#给返回添加返回头
        #读取文件,在当前文件路径下执行脚本,否则需要写文件的绝对路径;不然会找不到该json文件
        with open('1.json','rb') as f:
            #从json文件中读取数据成python对象
            res = json.load(f)
        #将读取的python对象转成json字符串发送给客户端
        flow.response.set_text(json.dumps(res))
        return

如果是接口测试用,有时候需要利用postman或其他apifox、apipost之类的工具导入接口,手动创建接口参数这些填写很麻烦,即使curl导入也不太方便,这时候我们可以把经过mitmproxy代理的相关参数进行整理并打印出来,然后复制到软件中。

    def request(self, flow):
        # 请求      
        ctx.log.error(f"【1】请求url:{flow.request.url}")
        ctx.log.error(f"【1】请求url(某些情况下地址是ip这时需要取pretty_url):{flow.request.pretty_url}")
        
        ctx.log.error(f"【1】请求host:{flow.request.host}")
        ctx.log.error(f"【1】请求host(某些情况下地址是ip这时需要取pretty_host):{flow.request.pretty_host}")
        ctx.log.error(f"【1】请求路径:{flow.request.path}")
        ctx.log.error(f"【1】请求content:{flow.request.content}")
        ctx.log.error("请求参数Params:")
        for k in flow.request.query:
            ctx.log.error(f"{k}:{flow.request.query[k]}")
        ctx.log.error(" ")
        ctx.log.error(f"【2】请求Cookie:")
        for k in flow.request.cookies:
            ctx.log.error(f"{k}:{flow.request.cookies[k]}")
        ctx.log.error(" ")
        ctx.log.error(f"【3】请求头:")
        for k in flow.request.headers:
            ctx.log.error(f"{k}:{flow.request.headers[k]}")
        ctx.log.error(" ")
        if 'Content-Type' in flow.request.headers:
            ctx.log.error(f"【4】请求类型:{flow.request.headers['Content-Type']}")
        else:
            ctx.log.error(f"【4】请求类型:无")
        #body 需要根据Content-Type去生成
        ctx.log.error("")
        ctx.log.error(f"【5】请求体文本:{flow.request.get_text()}")
        ctx.log.error("")
        if 'application/x-www-form-urlencoded' in str(flow.request.headers):
            #s = urllib.parse.unquote(flow.request.get_text().replace("=",":").replace("&","rn"))
            #ctx.log.error("【6】请求体x-www-form-urlencoded:n"+s)
            ctx.log.error("【6】请求体x-www-form-urlencoded:")
            for k in flow.request.urlencoded_form:
                ctx.log.error(f"{k}:{flow.request.urlencoded_form[k]}")
            ctx.log.error("")
        elif 'application/json' in str(flow.request.headers):
            ctx.log.error("【6】请求体json:n"+flow.request.get_text())
        else:
            ctx.log.error("【6】获取请求体失败,或请求体格式暂不支持")
        ctx.log.error("")
        pass

三、处理请求时常用的代码片段(收集自网络)

1、将url参数转为字典

    from urllib.parse import parse_qs, urlparse
    url = 'https://www.test.com?action=add&id=007&user=&wd=&action=add2'
    query = urlparse(url).query
    print("原始url:"+url)
    params = parse_qs(query)
    print(params)

运行结果:

原始url:https://www.test.com?action=add&id=007&user=&wd=&action=add2
{'action': ['add', 'add2'], 'id': ['007']}

2、将字典转为url参数

    from urllib.parse import urlencode
    params = {'action': 'add', 'id': 2}
    result = urlencode(params)
    print(result)

运行结果:

action=add&id=2

3、过滤函数,在所有处理请求前先过滤不匹配条件的请求

    def allowed_urls(ori_str):#判断提供的字符串/url 是否符合匹配条件
        check_dic=["ranjuan","ntest"]#需要包含的关键字
        check_pass=["act","99"]#需要排除的关键字, 匹配条件是既需要包含关键字,又不能出现要排除的关键字
        #return all(any(i in j for j in bvalue) for i in avalue)
        if any(e in ori_str for e in check_dic):
            if any(e in ori_str for e in check_pass):
                return False
            else:
                return True
        return False

    print(allowed_urls('http://ranjuan.cn?ction=1')) #True
    print(allowed_urls('http://ranjuan.cn?action=1'))#False
    print(allowed_urls('http://ranjuan.cn?cction=199'))#False
    print(allowed_urls('http://ranjuantesst.cn?ction=1'))#True
    print(allowed_urls('http://rantestjuan.cn?ction=1'))#True

4、md5函数

    import hashlib
    stringA='123456'
    str_md5 = hashlib.md5(stringA.encode(encoding='utf-8')).hexdigest()
    print(str_md5)

5、AES加密函数

aes加密使用会有一些坑,第一个就是python模块安装crypto后需要重命令相关的2个文件夹名字(首字母改成大写),然后再安装pycryptodome模块。另外就是密钥key建议一定要用16位长度,否则跨语言进行AES加解密时会自己python上写的可以加解密,但是对方那边就是解密不了(对方在自己的开发语言里面加解密也是正常的)。如果长度确实不够,那么填充key的方式两边需要确保一致才行!

    def pads(text,length=16):
        """
        #填充函数,使被加密数据的字节码长度是block_size的整数倍
        """
        print(f"pads明文"+str(text))
        count = len(text.encode('utf-8'))
        add = length - (count % length)
        entext = text + (chr(add) * add)
        print(f"pads结果"+str(entext.encode("utf8")))
        return entext.encode("utf8")


    from Crypto.Cipher import AES
    import base64
    #1、pip install Crypto
    #2、要去安装目录下把两个crypto的文件夹(我这是crypto、crypto-1.4.1.dist-info)首字母大写后再安装pycryptodome。
    #3、pip install pycryptodome
    #
    text = 'hello world'
    password = 'ABCdefG890123456' #秘钥,b就是表示为bytes类型;限制为16
    aes = AES.new(password.encode('utf-8'),AES.MODE_ECB) #密码正好长度为16时可不用填充,一定要16位很关键
    en_text = aes.encrypt(pads(text)) #加密明文
    encrypted_text = str(base64.encodebytes(en_text), encoding='utf-8')
    print("密文:",encrypted_text) 

基于互联网精神,在注明出处的前提下本站文章可自由转载!

本文链接:https://ranjuan.cn/mitmproxy-python-http-methods-use/

赞赏

微信赞赏支付宝赞赏

au3-auto-play-pic
axure-load-js-echarts
aliyun-python-sdk-mqtt-client