返回博客

开发者的mitmproxy:使用Python代码示例拦截和分析HTTPS流量

mitmproxy 是一个强大的工具,用于拦截和分析 HTTPS 流量。我们将讨论安装、SSL 证书配置、脚本编写和实际应用场景。

📅2026年8月5日
```html

您在调试移动应用程序时是否不明白它向服务器发送了哪些请求?需要在不同条件下测试 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/macOS
  • mitmproxy-ca-cert.cer——适用于 Windows
  • mitmproxy-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 中的自动化。

```