python结合mitmproxy做拦截代理

一、mitmproxy介绍

mitmproxy 就是用于 MITM 的 proxy,MITM 即中间人攻击(Man-in-the-middle attack)。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为。
mitmproxy可以拦截http和https请求,如果需要拦截https请求需要安装证书

二、mitmproxy安装(windows下)

1. 运行下面命令进行安装

pip install mitmproxy

安装成功后在Python\Scripts的目录下生成下面三个程序
在这里插入图片描述
2. 运行mitmdump命令
运行了上述命令后,在个人电脑用户目录下会生产一个.mitmproxy文件夹
在这里插入图片描述
windows配置证书,双击mimproxy-ca.p12,然后一路点击下一步即可。

3.设置浏览器代理

直接在浏览器设置代理,或者使用谷歌插件SwitchyOmega_Chromium.crx设置代理

4. 启动mitmproxy
mitmweb.exe和mitmproxy.exe是可视化的代理程序
mitmproxy.exe打开会在控制台显示所有代理请求,但是windows下不可用,
mitmweb.exe 会打开默认的浏览器并显示所有代理请求
mitmdump是mitmprxoy的命令行接口,同时还可以对接Python对请求进行处理,有了它我们可以不用手动截获和分析HTTP请求和响应,值需要写好请求和响应的处理逻辑即可

-p 端口号 可指定代理的端口号
在这里插入图片描述

三、mitmdump

1. 把截获的数据保存到文件中,使用-w参数即可

mitmdump -w 文件名

2. 指定脚本scripts.py来处理截获的数据,使用-s参数即可:

mitmdump -s xxx.py

3. 输出日志

ctx.log.info(str("这是一条info日志"))

四、代码示例

1. 拦截请求

from mitmproxy import http, ctx
from multiprocessing import Lock


class Filter:
    def __init__(self, filter_info):
        self.log_info = ""
        self.mutex = Lock()
        self.filter_info = filter_info
        self.response_file = None
        self.switch_on = False
        self.log_file = "log.txt"

    def log(self, info) -> None:
        self.log_info += f"{info}\n\n"

    def write_log(self, mode="w+") -> None:
        self.mutex.acquire()
        with open(self.log_file, mode) as f:
            f.write(self.log_info)
        self.mutex.release()

    def is_target_flow(self, flow: http.HTTPFlow) -> bool:
        for info in self.filter_info:
            if info["str_in_url"] in flow.request.url:
                self.log_file = info["log_file"]
                self.switch_on = info["switch_on"]
                if info["response_file"] != None:
                    self.response_file = info["response_file"]
                return True
        else:
            return False

    def modify_response(self, flow: http.HTTPFlow) -> http.HTTPFlow:
        if self.switch_on and self.response_file:
            with open(self.response_file, "r", encoding="utf-8") as f:
                flow.response.content = f.read().encode()
        return flow

    def request(self, flow: http.HTTPFlow) -> None:
        if self.is_target_flow(flow):
            self.log_info = ""
            self.log(f"——METHOD——\n{flow.request.method}")
            self.log(f"——HOST——\n{flow.request.pretty_host}")
            self.log(f"——URL——\n{flow.request.pretty_url}")
            query = [i + ":" + flow.request.query[i] + "\n" for i in flow.request.query]
            self.log(f"——QUERY STRING——\n{''.join(query)}")
            if flow.request.urlencoded_form:
                form = [i + ":" + flow.request.urlencoded_form[i] + "\n" for i in flow.request.urlencoded_form]
                self.log(f"——FORM——\n{''.join(form)}")
            self.write_log()

    def response(self, flow: http.HTTPFlow) -> None:
        if self.is_target_flow(flow):
            self.log_info = ""
            self.log(f"——RESPONSE before modified——\n{flow.response.content.decode(encoding='UTF-8')}")
            flow = self.modify_response(flow)
            self.log(f"——RESPONSE after modified——\n{flow.response.content.decode(encoding='UTF-8')}")
            self.write_log(mode="a")


filter_info = [
    {
        "str_in_url": "getSimpleNews",
        "log_file": "getSimpleNews_log.txt",
        "switch_on": True,
        "response_file": "getSimpleNews_response.txt",
    },
    {
        "str_in_url": "getQQNewsComment",
        "log_file": "getQQNewsComment_log.txt",
        "switch_on": True,
        "response_file": None,
    }
]
addons = [
    Filter(filter_info)
]

运行mitmproxy指定使用该脚本和端口号即可:

mitmdump -p 8888 -s xxx.py

在mitmdump运行时:

  1. 会拦截url中包含str_in_url字符串的请求

  2. 会把response.content修改为当前mitm运行所在目录下的response_file文件中的内容

  3. 打印信息在当前mitm运行所在目录下的log_file文件中

  4. 如果无需修改response设置switch_on为False即为开关关闭

  5. 如果不修改response的话response_file需要写None

2. 脚本2
实现如下需求:
因为百度搜索是不靠谱的,所有当客户端发起百度搜索时,记录下用户的搜索词,再修改请求,将搜索词改为“360 搜索”;
因为 360 搜索还是不靠谱的,所有当客户端访问 360 搜索时,将页面中所有“搜索”字样改为“请使用谷歌”。
因为谷歌是个不存在的网站,所有就不要浪费时间去尝试连接服务端了,所有当发现客户端试图访问谷歌时,直接断开连接。

# 1. 需要篡改客户端请求,所以实现一个 request 事件:
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"))
    # 替换搜索词为“360搜索”
    flow.request.query.set_all("wd", ["360搜索"])
# 2.需要篡改服务端响应,所以实现一个 response 事件:
def response(self, flow: mitmproxy.http.HTTPFlow):
    # 忽略非 360 搜索地址
    if flow.request.host != "www.so.com":
        return

    # 将响应中所有“搜索”替换为“请使用谷歌”
    text = flow.response.get_text()
    text = text.replace("搜索", "请使用谷歌")
    flow.response.set_text(text)
# 3.需求需要拒绝客户端请求,所以实现一个 http_connect 事件:
def http_connect(self, flow: mitmproxy.http.HTTPFlow):
    # 确认客户端是想访问 www.google.com
    if flow.request.host == "www.google.com":
        # 返回一个非 2xx 响应断开连接
        flow.response = http.HTTPResponse.make(404)

创建一个 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(),
]

将三个文件放在相同的文件夹,在该文件夹内启动命令行,运行:

mitmdump -p 8888 -s addons.py

关闭所有 Chrome 窗口,从命令行中启动 Chrome 并指定代理且忽略证书错误。

五、常用接口

#http.HTTPFlow 实例 flow
flow.request.headers #获取所有头信息,包含Host、User-Agent、Content-type等字段
flow.request.url #完整的请求地址,包含域名及请求参数,但是不包含放在body里面的请求参数
flow.request.pretty_url #同flow.request.url目前没看出什么差别
flow.request.host #域名
flow.request.method #请求方式。POST、GET等
flow.request.scheme #什么请求 ,如https
flow.request.path # 请求的路径,url除域名之外的内容
flow.request.get_text() #请求中body内容,有一些http会把请求参数放在body里面,那么可通过此方法获取,返回字典类型
flow.request.query #返回MultiDictView类型的数据,url直接带的键值参数
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.multipart_form #MultiDictView,content-type:multipart/form-data
时的请求参数,不包含url直接带的键值参数
#以上均为获取request信息的一些常用方法,对于response,同理
flow.response.status_code #状态码
flow.response.text#返回内容,已解码
flow.response.content #返回内容,二进制
flow.response.setText()#修改返回内容,不需要转码
  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值