mitmproxy 作为业界主流的中间人代理工具,早已超越了 "图形化抓包" 的基础定位。其真正的核心能力,在于通过 Python 自定义脚本实现自动化请求 / 响应拦截、动态篡改、流量 Mock 与协议扩展。相比 Fiddler、Charles 等工具的手动改包,mitmproxy 脚本可以实现毫秒级自动化改包、复杂规则匹配、批量流量处理,是逆向分析、接口调试、渗透测试的核心利器。
本文从核心机制出发,系统讲解 mitmproxy 自定义脚本的拦截改包玩法,从基础请求 / 响应修改到高级流式处理、条件过滤、Mock 响应,附完整可运行代码示例。
一、mitmproxy 脚本的核心机制:Addon 与事件钩子
mitmproxy 的脚本基于 Addon(插件)架构,所有自定义逻辑都通过注册事件钩子实现。当代理处理 HTTP/HTTPS 流量时,会在请求生命周期的不同节点触发对应事件,我们只需要编写对应事件的处理函数即可。
1.1 核心事件钩子
最常用的 HTTP 流量事件有两个:
request(flow):请求到达代理、尚未发送给服务器时触发,用于修改请求。response(flow):响应返回代理、尚未发给客户端时触发,用于修改响应。
其他高频事件:
http_connect:处理 CONNECT 请求(HTTPS 握手阶段)error:流量出错时触发done:单条流量处理完成时触发
1.2 脚本运行方式
将逻辑保存为 .py 文件,通过 mitmdump(mitmproxy 的命令行版本)加载运行:
mitmdump -s custom_intercept.py -p 8080
也可以在 mitmproxy 交互界面按 D 动态加载脚本。
1.3 核心操作对象:Flow
每个 HTTP 请求对应一个 flow 对象,包含完整的请求和响应上下文:
flow.request:请求对象,包含 method、url、headers、content、text、cookies 等可读写属性flow.response:响应对象,包含 status_code、headers、content、text 等可读写属性
修改这些属性的值,就等价于完成了 "改包" 操作。
二、基础实战:请求拦截与篡改
请求拦截是最常用的场景,比如修改请求头、替换 URL 参数、篡改 POST 数据、添加自定义标识等。
2.1 修改请求头:添加 / 替换 Header
下面的脚本仅针对目标域名添加自定义请求头,并修改 User-Agent:
"""
请求头篡改示例
"""
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
# 只处理目标域名的请求,避免全局污染
if "api.example.com" in flow.request.host:
# 添加自定义请求头
flow.request.headers["X-Custom-Token"] = "test_token_123456"
# 覆盖 User-Agent
flow.request.headers["User-Agent"] = "Custom-Agent/1.0"
# 删除指定请求头
if "Referer" in flow.request.headers:
del flow.request.headers["Referer"]
2.2 修改 GET 请求参数
通过 flow.request.query 可以直接操作 URL 查询参数:
def request(flow: http.HTTPFlow) -> None:
if flow.request.path.startswith("/search"):
# 修改/新增 query 参数
flow.request.query["page"] = "100"
flow.request.query["keyword"] = "modified_keyword"
# 删除指定参数
if "debug" in flow.request.query:
del flow.request.query["debug"]
2.3 修改 POST 请求体(JSON / 表单)
对于 JSON 格式的请求体,解析后修改字段再重新赋值即可:
import json
def request(flow: http.HTTPFlow) -> None:
if flow.request.method == "POST" and "/submit" in flow.request.path:
content_type = flow.request.headers.get("Content-Type", "")
if "application/json" in content_type:
try:
# 解析 JSON 请求体
data = json.loads(flow.request.text)
# 修改业务字段
data["amount"] = 999
data["user_id"] = "modified_user"
# 写回,mitmproxy 会自动处理编码
flow.request.text = json.dumps(data, ensure_ascii=False)
except json.JSONDecodeError:
pass
对于表单格式(x-www-form-urlencoded),可以用 urlencoded_form 直接操作:
def request(flow: http.HTTPFlow) -> None:
if flow.request.method == "POST":
flow.request.urlencoded_form["username"] = "admin"
flow.request.urlencoded_form["password"] = "123456"
三、响应拦截:篡改返回数据
响应拦截用于修改服务器返回的内容,比如修改接口返回码、替换页面内容、注入 JS、篡改业务数据等。
3.1 修改响应状态码与响应头
def response(flow: http.HTTPFlow) -> None:
if "/block" in flow.request.path:
# 将 403 状态码改为 200
flow.response.status_code = 200
# 添加跨域响应头
flow.response.headers["Access-Control-Allow-Origin"] = "*"
flow.response.headers["Access-Control-Allow-Credentials"] = "true"
3.2 修改 JSON 响应体
这是接口调试最常用的场景,比如修改订单金额、修改用户等级、绕过状态校验:
import json
def response(flow: http.HTTPFlow) -> None:
if "/api/user/info" in flow.request.path:
try:
data = json.loads(flow.response.text)
# 修改业务字段
data["data"]["vip_level"] = 99
data["data"]["balance"] = 99999.99
data["code"] = 0
# 写回响应体
flow.response.text = json.dumps(data, ensure_ascii=False)
except json.JSONDecodeError:
pass
3.3 替换网页内容 / 注入脚本
针对 HTML 页面,可以批量替换关键字,或者注入自定义 JS/CSS:
def response(flow: http.HTTPFlow) -> None:
content_type = flow.response.headers.get("Content-Type", "")
if "text/html" in content_type:
# 批量替换页面文字
flow.response.text = flow.response.text.replace("原价", "优惠价")
# 注入自定义 JS 脚本
inject_js = '<script>console.log("mitmproxy注入成功")</script>'
flow.response.text = flow.response.text.replace("</body>", inject_js + "</body>")
注意:使用
flow.response.text时,mitmproxy 会自动处理 gzip/deflate 压缩与字符编码;如果直接操作flow.response.content(字节),需要手动处理压缩。
四、高级拦截技巧
掌握基础修改后,结合条件过滤、流式处理、Mock 响应等技巧,可以应对更复杂的业务场景。
4.1 精准条件过滤:只拦截目标流量
不要全局修改所有流量,建议通过域名、路径、方法、Header 等多维度组合过滤:
import re
def request(flow: http.HTTPFlow) -> None:
# 组合条件:POST 方法 + 指定域名 + 路径正则匹配
if (flow.request.method == "POST"
and flow.request.host == "api.target.com"
and re.match(r"/api/v\d+/order/create", flow.request.path)):
# 执行改包逻辑
pass
4.2 请求重定向:转发到其他环境
将请求透明转发到测试环境或本地服务,无需修改客户端任何配置:
def request(flow: http.HTTPFlow) -> None:
if flow.request.host == "prod.example.com":
# 重定向到测试环境域名
flow.request.host = "test.example.com"
# 也可以修改端口或完整 URL
# flow.request.port = 8081
# flow.request.url = "http://localhost:3000/mock/api"
4.3 直接 Mock 响应:跳过后端请求
无需等待服务器返回,直接在代理层返回自定义响应,常用于前端 Mock 或绕过后端校验:
from mitmproxy import http
import json
def request(flow: http.HTTPFlow) -> None:
if "/api/mock/data" in flow.request.path:
# 构造自定义响应
mock_body = {
"code": 0,
"msg": "success",
"data": {"id": 1, "name": "mock数据"}
}
flow.response = http.Response.make(
200, # 状态码
json.dumps(mock_body, ensure_ascii=False).encode("utf-8"), # 响应体
{"Content-Type": "application/json; charset=utf-8"} # 响应头
)
注意:在
request事件中直接给flow.response赋值,会中断请求转发,直接将结果返回给客户端。
4.4 流式拦截:处理大文件 / 流媒体
默认情况下 mitmproxy 会等待完整响应加载后再触发 response 事件,大文件会占用大量内存。对于流媒体、大文件下载,可以使用流式钩子边接收边修改:
def responseheaders(flow: http.HTTPFlow) -> None:
# 开启流式处理模式
flow.response.stream = True
def response_stream(flow: http.HTTPFlow, chunk: bytes) -> bytes:
# 对每个数据块进行修改
if b"old_string" in chunk:
chunk = chunk.replace(b"old_string", b"new_string")
return chunk
4.5 Cookie 与会话操作
批量修改或注入 Cookie,实现会话劫持、追踪绕过等效果:
def request(flow: http.HTTPFlow) -> None:
# 注入会话 Cookie
flow.request.cookies["session_id"] = "hijacked_session_001"
# 删除追踪 Cookie
if "track_id" in flow.request.cookies:
del flow.request.cookies["track_id"]
五、进阶:完整 Addon 插件结构
简单脚本可以直接写函数,复杂逻辑建议封装成 Addon 类,支持状态保持、配置项、自定义命令等高级能力。
"""
完整 Addon 示例:可配置的接口篡改插件
"""
from mitmproxy import http, ctx
import json
class DataModifier:
def __init__(self):
# 内部状态计数器
self.modify_count = 0
def load(self, loader):
# 注册命令行配置项
loader.add_option(
name="enable_modify",
typespec=bool,
default=True,
help="是否启用数据修改"
)
def response(self, flow: http.HTTPFlow) -> None:
# 读取配置项,控制开关
if not ctx.options.enable_modify:
return
if "/api/order/list" in flow.request.path:
try:
data = json.loads(flow.response.text)
data["data"]["total"] = 999
flow.response.text = json.dumps(data, ensure_ascii=False)
self.modify_count += 1
ctx.log.info(f"已修改第 {self.modify_count} 个响应")
except json.JSONDecodeError:
pass
# 注册 Addon 插件
addons = [DataModifier()]
运行时通过参数动态控制:
mitmdump -s modifier_addon.py --set enable_modify=true
六、常见坑与最佳实践
- text 与 content 的区别
text:字符串类型,自动处理编码和解压缩,适合文本类数据。content:字节类型,原始二进制数据,适合图片、文件等二进制数据。- 修改二进制内容请操作
content,避免编码损坏。
- HTTPS 证书问题
- 必须在客户端安装 mitmproxy 根证书,否则 HTTPS 流量无法解密和修改。
- 对于开启证书绑定(SSL Pinning)的 App,需要配合 Frida 等工具绕过。
- 性能注意事项
- 避免在事件函数中做耗时操作(如网络请求、复杂计算),会拖慢代理速度。
- 大文件、流媒体务必开启流式处理模式。
- 编码与乱码
- 修改中文时确保使用
ensure_ascii=False,并指定正确的 charset。 - 遇到非 UTF-8 编码的响应,手动通过
flow.response.content.decode("gbk")处理。
- 修改中文时确保使用
- 调试技巧
- 使用
ctx.log.info()输出日志,不要用 print。 - 配合
mitmweb图形界面查看流量,验证改包是否生效。
- 使用
写在最后
mitmproxy 的自定义脚本能力,本质上是给 HTTP 流量加上了一层可编程的中间层。从简单的参数修改到复杂的协议逆向、流量劫持、自动化测试,只要能抽象成 "请求 / 响应处理逻辑",都可以通过 mitmproxy 脚本实现。
以上覆盖了 90% 以上的改包场景,建议从基础示例开始,逐步叠加条件过滤和业务逻辑,最终打造属于自己的专属代理工具。