您在调试移动应用程序时是否不明白它向服务器发送了哪些请求?需要在不同条件下测试 API 的行为,或者拦截响应并实时替换数据?mitmproxy 可以解决所有这些问题——这是一个免费的开源工具,可以完全控制客户端和服务器之间的 HTTP 和 HTTPS 流量。
什么是 mitmproxy,开发者为什么需要它
mitmproxy 是一个交互式的 MITM 代理(中间人代理),是用 Python 编写的开源工具。它作为您的应用程序和服务器之间的中介:拦截所有请求和响应,允许您查看、修改、重放和保存它们。
mitmproxy 与普通代理服务器的主要区别在于它能够处理加密的 HTTPS 流量。该工具动态生成每个域的 SSL 证书,这使得可以实时解密流量,而不会干扰应用程序的正常运行。
以下是开发者使用 mitmproxy 解决的典型任务:
- API 调试——查看确切的请求和响应,包括头部、主体、状态码。
- 逆向工程——分析第三方应用程序和服务的工作原理。
- 测试——替换服务器响应以检查边界情况。
- 自动化——编写脚本根据条件修改流量。
- 录制和重放——保存会话并在没有真实服务器的情况下重放。
- 安全分析——检查应用程序是否传递了多余的数据。
重要的是要理解
mitmproxy 是一个用于合法测试和开发的工具。仅将其用于分析您开发或有权测试的应用程序的流量。未经许可拦截他人的流量是违法的。
该工具有三种变体:控制台交互界面 mitmproxy、Web 界面 mitmweb 和命令行工具 mitmdump。这三者使用相同的核心,并支持 Python 脚本。
在 Windows、macOS 和 Linux 上安装 mitmproxy
mitmproxy 可以通过多种方式安装。推荐使用 pip——这可以确保您获得最新版本并轻松更新。
通过 pip 安装(通用方法)
需要 Python 3.9 或更高版本。检查 Python 版本:
python --version
# 或者
python3 --version
安装 mitmproxy:
pip install mitmproxy
安装后检查:
mitmproxy --version
# 应该输出:mitmproxy 10.x.x
通过包管理器安装
macOS(Homebrew):
brew install mitmproxy
Linux(Ubuntu/Debian):
sudo apt install mitmproxy
# 或者通过 snap 获取最新版本:
sudo snap install mitmproxy
Windows: 从官方网站 mitmproxy.org 下载安装程序,或在 PowerShell 中以管理员权限使用 pip。
启动和基本检查
默认情况下,mitmproxy 监听端口 8080。启动 Web 界面以开始工作:
# 在端口 8080 启动 Web 界面
mitmweb
# 在其他端口启动
mitmweb --listen-port 9090
# 控制台界面
mitmproxy
启动 mitmweb 后,打开浏览器访问 http://127.0.0.1:8081——这是用于查看流量的 Web 界面。代理本身在端口 8080 上运行。
配置 SSL 证书以拦截 HTTPS
拦截 HTTPS 流量需要将 mitmproxy 的根证书安装到系统或浏览器中。没有这一步,浏览器将显示不安全连接的警告,许多应用程序将完全拒绝工作。
技术上是如何工作的
在第一次启动 mitmproxy 时,它会自动创建根 CA 证书并将其保存在目录 ~/.mitmproxy/ 中。当客户端通过代理连接到 HTTPS 网站时,mitmproxy 会实时生成该域的证书,并用其 CA 签名。如果 CA 被添加到受信任的列表中,客户端就会信任该证书——解密过程是透明的。
在系统中安装证书
证书位于 ~/.mitmproxy/:
mitmproxy-ca-cert.pem——适用于 Linux/macOSmitmproxy-ca-cert.cer——适用于 Windowsmitmproxy-ca-cert.p12——适用于 iOS
macOS:
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain \
~/.mitmproxy/mitmproxy-ca-cert.pem
Linux(Ubuntu/Debian):
sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem \
/usr/local/share/ca-certificates/mitmproxy.crt
sudo update-ca-certificates
Windows: 双击文件 mitmproxy-ca-cert.cer → “安装证书” → “本地计算机” → “受信任的根证书颁发机构”。
在 Firefox 浏览器中安装证书
Firefox 使用自己的证书存储。前往:设置 → 隐私与安全 → 证书 → 查看证书 → 证书颁发机构 → 导入。选择文件 mitmproxy-ca-cert.pem 并勾选“在识别网站时信任”。
检查工作
将浏览器配置为使用代理 127.0.0.1:8080,并打开任何 HTTPS 网站。在 mitmweb 界面中,您应该看到解密的流量。或者——通过配置的代理打开 http://mitm.it:mitmproxy 将显示有关您平台的证书安装说明。
三个接口:mitmproxy、mitmweb 和 mitmdump
mitmproxy 包含三个具有不同接口的工具,适用于不同的工作场景。了解这些差异将有助于为每个任务选择正确的工具。
| 工具 | 接口 | 何时使用 | 特点 |
|---|---|---|---|
mitmproxy |
控制台 TUI | 在终端中进行交互式调试 | 需要支持颜色的终端,强大的过滤器 |
mitmweb |
Web 浏览器 | 可视化流量分析 | 友好的 UI,支持过滤器,导出 |
mitmdump |
CLI(stdout) | 脚本、CI/CD、自动化 | 没有交互性,输出到文件或管道 |
有用的启动标志
# 将流量记录到文件
mitmdump -w traffic.dump
# 重放记录的流量
mitmdump -r traffic.dump
# 过滤:仅请求特定域
mitmproxy --filter "~d api.example.com"
# 以透明代理模式启动
mitmproxy --mode transparent
# 作为上游代理启动(代理链)
mitmproxy --mode upstream:http://upstream-proxy:8080
# 指定特定端口
mitmweb --listen-port 9090 --web-port 9091
# 启动时带脚本
mitmproxy -s my_script.py
mitmproxy 过滤器语法
mitmproxy 支持强大的过滤语言来选择所需的请求:
# ~d — 按域过滤
~d api.example.com
# ~u — 按 URL 过滤(正则表达式)
~u /api/v2/users
# ~m — 按 HTTP 方法过滤
~m POST
# ~s — 仅响应
~s ~c 404
# ~c — 按状态码过滤
~c 500
# 组合(AND)
~d api.example.com & ~m POST
# 组合(OR)
~c 404 | ~c 500
# NOT
!~d static.example.com
用 Python 编写脚本:拦截和修改流量
脚本是 mitmproxy 的主要强项。通过它们,您可以自动修改请求和响应,以所需格式记录数据,模拟服务器错误等。脚本使用 Python 编写,并利用事件驱动模型。
主要事件(钩子)
| 钩子 | 何时调用 | 对象 |
|---|---|---|
request |
从客户端收到请求 | flow.request |
response |
从服务器收到响应 | flow.response |
error |
连接错误 | flow.error |
tls_start_client |
与客户端的 TLS 握手开始 | tls_start |
示例 1:将请求记录到文件
# logger.py
import mitmproxy.http
import json
from datetime import datetime
def request(flow: mitmproxy.http.HTTPFlow) -> None:
"""将每个请求记录到 JSON 文件中。"""
log_entry = {
"timestamp": datetime.now().isoformat(),
"method": flow.request.method,
"url": flow.request.pretty_url,
"headers": dict(flow.request.headers),
"body": flow.request.text if flow.request.text else None
}
with open("requests.log", "a") as f:
f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
def response(flow: mitmproxy.http.HTTPFlow) -> None:
"""记录错误代码的响应。"""
if flow.response.status_code >= 400:
print(f"[ERROR] {flow.request.method} {flow.request.pretty_url} "
f"-> {flow.response.status_code}")
启动脚本:
mitmproxy -s logger.py
示例 2:修改请求——替换头部
# modify_headers.py
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
"""替换 User-Agent 并添加自定义头部。"""
if "api.example.com" in flow.request.pretty_host:
# 替换 User-Agent
flow.request.headers["User-Agent"] = (
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1"
)
# 添加授权头部
flow.request.headers["X-Custom-Token"] = "test-token-12345"
# 删除头部
if "X-Debug-Info" in flow.request.headers:
del flow.request.headers["X-Debug-Info"]
示例 3:替换服务器响应(模拟)
# mock_response.py
from mitmproxy import http
import json
def request(flow: http.HTTPFlow) -> None:
"""拦截请求并返回模拟响应,而不访问服务器。"""
if flow.request.pretty_url.endswith("/api/v1/user/profile"):
# 创建模拟响应
mock_data = {
"id": 42,
"name": "Test User",
"email": "[email protected]",
"premium": True # 测试高级功能
}
flow.response = http.Response.make(
200, # 状态码
json.dumps(mock_data), # 响应主体
{"Content-Type": "application/json"} # 头部
)
def response(flow: http.HTTPFlow) -> None:
"""修改真实的服务器响应。"""
if "/api/v1/products" in flow.request.pretty_url:
try:
data = json.loads(flow.response.text)
# 向每个产品添加字段
for product in data.get("items", []):
product["debug_info"] = "intercepted"
flow.response.text = json.dumps(data)
except (json.JSONDecodeError, KeyError):
pass
示例 4:模拟慢连接和错误
# chaos_testing.py
from mitmproxy import http
import time
import random
def response(flow: http.HTTPFlow) -> None:
"""混沌工程:随机延迟和错误以进行测试。"""
# 添加 0 到 2 秒的随机延迟
if "api.example.com" in flow.request.pretty_host:
delay = random.uniform(0, 2.0)
time.sleep(delay)
# 10% 的概率返回 503
if random.random() < 0.1:
flow.response = http.Response.make(
503,
json.dumps({"error": "Service Unavailable"}),
{"Content-Type": "application/json"}
)
拦截移动应用程序的流量
分析移动应用程序的流量是使用 mitmproxy 时最常见的任务之一。这在逆向工程移动应用程序的 API 或在真实设备上测试自己的应用程序时特别有用。
在 Android 上配置
步骤 1. 确保手机和计算机在同一 Wi-Fi 网络中。
步骤 2. 在计算机上启动 mitmproxy:
mitmweb --listen-host 0.0.0.0 --listen-port 8080
步骤 3. 在 Android 上:设置 → Wi-Fi → 长按网络 → 修改网络 → 高级 → 代理 → 手动。输入计算机的 IP 和端口 8080。
步骤 4. 在 Android 上安装证书:在设备上打开浏览器,访问 http://mitm.it 并下载 Android 的证书。然后:设置 → 安全 → 安装证书 → CA 证书。
Android 7+ 和证书钉扎
从 Android 7.0 开始,应用程序默认不信任用户 CA 证书。要拦截这些应用程序的流量,需要 root 权限或在 APK 中修改 network_security_config.xml。对于具有 SSL 钉扎的应用程序,请使用 Frida 或 Xposed Framework 绕过证书检查。
在 iOS 上配置
步骤 1. 类似于 Android 配置代理:设置 → Wi-Fi → 点击网络旁边的 (i) → 设置代理 → 手动。
步骤 2. 打开 Safari 并访问 http://mitm.it——下载 iOS 的证书。
步骤 3. 安装配置文件:设置 → 已下载的配置文件 → 安装。
步骤 4. 启用对证书的信任:设置 → 一般 → 关于本机 → 信任证书——为 mitmproxy 启用开关。
通过 Python 拦截特定应用程序的流量
# mobile_app_analyzer.py
from mitmproxy import http
import json
import re
# 目标应用程序的域
TARGET_DOMAINS = ["api.myapp.com", "cdn.myapp.com"]
def response(flow: http.HTTPFlow) -> None:
"""分析移动应用程序的流量。"""
host = flow.request.pretty_host
if not any(domain in host for domain in TARGET_DOMAINS):
return
# 提取 JSON 响应
content_type = flow.response.headers.get("content-type", "")
if "application/json" in content_type:
try:
data = json.loads(flow.response.text)
print(f"\n{'='*60}")
print(f"URL: {flow.request.pretty_url}")
print(f"Status: {flow.response.status_code}")
print(f"Response: {json.dumps(data, indent=2, ensure_ascii=False)}")
except json.JSONDecodeError:
pass
# 查找请求头中的令牌
auth_header = flow.request.headers.get("authorization", "")
if auth_header:
print(f"[AUTH] Token found: {auth_header[:50]}...")
代理链:mitmproxy + 上游代理
一个强大的场景是将 mitmproxy 与外部代理服务器结合使用。这允许同时拦截和分析流量(通过 mitmproxy)并通过外部 IP 地址(通过上游代理)转发流量。这种方案在测试地理相关的 API 或开发必须通过代理工作的应用程序时非常有用。
上游代理模式
# 将所有流量通过上游 HTTP 代理转发
mitmproxy --mode upstream:http://proxy-host:port
# 上游 SOCKS5 代理
mitmproxy --mode upstream:socks5://proxy-host:port
# 带身份验证
mitmproxy --mode upstream:http://user:password@proxy-host:port
# 通过 mitmweb 使用上游
mitmweb --mode upstream:http://proxy-host:port
在此模式下,mitmproxy 在本地接收请求,解密 HTTPS,允许您分析和修改它们,然后通过外部代理转发。这在测试仅在特定区域可用的 API 时特别方便。
对于此类任务,住宅代理 非常合适——它们具有来自所需国家的真实家庭用户的 IP 地址,这使得可以正确测试地理相关的 API 响应。
在脚本中动态选择上游代理
# dynamic_upstream.py
from mitmproxy import http
from mitmproxy.net.server_spec import ServerSpec
# 轮换的代理列表
PROXY_LIST = [
"http://proxy1.example.com:8080",
"http://proxy2.example.com:8080",
"http://proxy3.example.com:8080",
]
proxy_index = 0
def request(flow: http.HTTPFlow) -> None:
"""为每个请求轮换上游代理。"""
global proxy_index
# 通过不同的代理转发请求到 API
if "api.target.com" in flow.request.pretty_host:
proxy_url = PROXY_LIST[proxy_index % len(PROXY_LIST)]
proxy_index += 1
flow.live.change_upstream_proxy_server(
ServerSpec.from_url(proxy_url)
)
print(f"Using proxy: {proxy_url} for {flow.request.pretty_url}")
透明代理(透明模式)
在透明模式下,应用程序不知道其流量正在被拦截——不需要在设置中配置代理。这需要在操作系统级别配置 iptables/pf:
# 以透明模式启动
mitmproxy --mode transparent --listen-port 8080
# 配置 iptables 以重定向流量(Linux)
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 8080
sudo iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-port 8080
实际应用场景 mitmproxy
让我们看看开发者在实际项目中使用 mitmproxy 解决的具体任务。
场景 1:在不更改服务器的情况下测试 API
想象一下:您需要检查前端如何处理空数据数组的响应或 401 授权错误,但在测试服务器上重现这一点很困难。mitmproxy 允许您实时替换响应:
# test_edge_cases.py
from mitmproxy import http
import json
def response(flow: http.HTTPFlow) -> None:
url = flow.request.pretty_url
# 测试:空商品列表
if "/api/products" in url and "test_empty=1" in url:
flow.response.text = json.dumps({"items": [], "total": 0})
# 测试:令牌过期
if "/api/user" in url and "test_auth=1" in url:
flow.response = http.Response.make(
401,
json.dumps({"error": "Token expired", "code": "AUTH_001"}),
{"Content-Type": "application/json"}
)
# 测试:请求超限
if "/api/" in url and "test_rate=1" in url:
flow.response = http.Response.make(
429,
json.dumps({"error": "Too Many Requests", "retry_after": 60}),
{"Content-Type": "application/json",
"Retry-After": "60"}
)
场景 2:记录和重放会话
对于创建测试夹具或在没有真实服务器的情况下演示功能非常有用:
# 将会话记录到文件
mitmdump -w session.dump --filter "~d api.example.com"
# 重放记录的会话(离线)
mitmdump -r session.dump
# 转换为 HAR 格式以进行分析
mitmdump -r session.dump --flow-detail 3 > session.txt
场景 3:自动化 API 文档
# api_documenter.py
from mitmproxy import http
import json
from collections import defaultdict
# 用于积累端点信息的字典
endpoints = defaultdict(lambda: {"methods": set(), "status_codes": set(),
"request_fields": set(), "response_fields": set()})
def _extract_fields(data, prefix=""):
"""递归提取 JSON 中的字段。"""
fields = set()
if isinstance(data, dict):
for key, value in data.items():
full_key = f"{prefix}.{key}" if prefix else key
fields.add(full_key)
fields.update(_extract_fields(value, full_key))
elif isinstance(data, list) and data:
fields.update(_extract_fields(data[0], prefix))
return fields
def response(flow: http.HTTPFlow) -> None:
if "api.example.com" not in flow.request.pretty_host:
return
# 标准化 URL(去掉 ID)
import re
path = re.sub(r'/\d+', '/{id}', flow.request.path)
endpoint = f"{flow.request.method} {path}"
ep = endpoints[endpoint]
ep["methods"].add(flow.request.method)
ep["status_codes"].add(flow.response.status_code)
# 提取请求字段
if flow.request.text:
try:
req_data = json.loads(flow.request.text)
ep["request_fields"].update(_extract_fields(req_data))
except json.JSONDecodeError:
pass
# 提取响应字段
if flow.response.text:
try:
resp_data = json.loads(flow.response.text)
ep["response_fields"].update(_extract_fields(resp_data))
except json.JSONDecodeError:
pass
def done():
"""完成时输出文档。"""
print("\n=== API DOCUMENTATION ===\n")
for endpoint, info in sorted(endpoints.items()):
print(f"Endpoint: {endpoint}")
print(f" Status codes: {sorted(info['status_codes'])}")
if info["request_fields"]:
print(f" Request fields: {sorted(info['request_fields'])}")
if info["response_fields"]:
print(f" Response fields: {sorted(info['response_fields'])}")
print()
场景 4:测试地理相关的响应
在开发具有区域内容的应用程序时,检查 API 如何响应来自不同国家的请求非常重要。为此,mitmproxy 在与 数据中心代理 的上游模式下运行,快速可靠地模拟来自特定国家的请求。
# geo_test.py
from mitmproxy import http
def response(flow: http.HTTPFlow) -> None:
"""记录地理相关的头部和数据。"""
# 查看服务器返回的内容
geo_headers = ["cf-ipcountry", "x-country", "x-geo-country"]
for header in geo_headers:
value = flow.response.headers.get(header)
if value:
print(f"[GEO] {header}: {value} | URL: {flow.request.pretty_url}")
# 在响应中查找货币和区域的提及
if flow.response.text:
import re
currencies = re.findall(r'"currency":\s*"([A-Z]{3})"', flow.response.text)
locales = re.findall(r'"locale":\s*"([a-z]{2}-[A-Z]{2})"', flow.response.text)
if currencies:
print(f"[CURRENCY] {currencies}")
if locales:
print(f"[LOCALE] {locales}")
场景 5:在 CI/CD 中使用 mitmproxy
mitmdump 非常适合在 CI/CD 流水线中的集成测试——它作为后台进程运行,记录流量并与测试一起结束:
#!/bin/bash
# ci_test.sh
# 在后台启动 mitmdump
mitmdump -w test_traffic.dump -s ci_assertions.py &
MITM_PID=$!
# 给代理一些时间启动
sleep 1
# 启动带代理的测试
export HTTP_PROXY=http://127.0.0.1:8080
export HTTPS_PROXY=http://127.0.0.1:8080
pytest tests/integration/ -v
# 停止 mitmdump
kill $MITM_PID
# 分析记录的流量
mitmdump -r test_traffic.dump -s analyze_traffic.py
脚本 ci_assertions.py 可以检查应用程序是否没有发出多余的请求,是否没有以未加密的形式传递敏感数据,以及是否遵循 API 合同。
场景 6:解析器流量分析
在开发解析器时,mitmproxy 有助于了解浏览器在加载页面时发出的确切请求——包括对 API 的 XHR/fetch 请求,这些请求在 HTML 源代码中不可见。这使得可以直接访问网站的 API,而不是解析 HTML。在开发此类解决方案时,通常使用 住宅代理 进行 IP 轮换,以避免在收集数据时被封锁。
结论
mitmproxy 是开发者处理 HTTP/HTTPS 流量的最强大工具之一。它结合了调试器、测试环境、API 文档和安全分析工具的功能。三个接口——控制台、Web 和 CLI——涵盖了所有场景:从交互式调试到 CI/CD 中的自动化。
```