モバイルアプリをデバッグしていて、サーバーに送信されるリクエストがわからないですか?異なる条件下でAPIの動作をテストしたり、レスポンスをインターセプトしてデータをリアルタイムで置き換えたりする必要がありますか?mitmproxyはこれらの課題をすべて解決します。これは、クライアントとサーバー間のHTTPおよびHTTPSトラフィックを完全に制御できるオープンソースの無料ツールです。
mitmproxyとは何か、開発者にとっての必要性
mitmproxyは、Pythonで書かれたオープンソースのインタラクティブなMITMプロキシ(Man-In-The-Middle Proxy)です。これは、アプリケーションとサーバーの間の仲介者として機能し、すべてのリクエストとレスポンスをインターセプトし、表示、変更、再生、保存することを可能にします。
mitmproxyの主な違いは、通常のプロキシサーバーと異なり、暗号化されたHTTPSトラフィックを扱える点です。このツールは、各ドメインのためにSSL証明書を動的に生成し、アプリケーションの動作を妨げることなくトラフィックをリアルタイムで復号化することを可能にします。
以下は、開発者がmitmproxyを使用して解決する典型的なタスクです:
- APIデバッグ — リクエストとレスポンスの正確な内容を確認できます(ヘッダー、ボディ、ステータスコードを含む)。
- リバースエンジニアリング — サードパーティのアプリケーションやサービスの動作を分析します。
- テスト — サーバーのレスポンスを置き換えて境界ケースを検証します。
- 自動化 — 条件に基づいてトラフィックを変更するスクリプトを作成します。
- セッションの記録と再生 — セッションを保存し、実際のサーバーなしで再生します。
- セキュリティ分析 — アプリケーションが不要なデータを送信していないか確認します。
理解しておくべきこと
mitmproxyは合法的なテストと開発のためのツールです。開発またはテストする権利のあるアプリケーションのトラフィックを分析するためにのみ使用してください。他人のトラフィックを許可なくインターセプトすることは法律に違反します。
このツールは、インタラクティブなコンソールインターフェース mitmproxy、ウェブインターフェース 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をリッスンします。作業を開始するためにウェブインターフェースを起動します:
# ポート8080でウェブインターフェースを起動
mitmweb
# 別のポートで起動
mitmweb --listen-port 9090
# コンソールインターフェース
mitmproxy
mitmwebを起動した後、ブラウザでhttp://127.0.0.1:8081にアクセスしてください。これはトラフィックを表示するためのウェブインターフェースです。プロキシ自体はポート8080で動作しています。
HTTPSインターセプトのためのSSL証明書の設定
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 |
ウェブブラウザ | トラフィックの視覚的分析 | 使いやすいUI、フィルターのサポート、エクスポート |
mitmdump |
CLI(stdout) | スクリプト、CI/CD、自動化 | インタラクティブ性なし、ファイルまたはパイプへの出力 |
便利な起動フラグ
# トラフィックをファイルに記録
mitmdump -w traffic.dump
# 記録されたトラフィックを再生
mitmdump -r traffic.dump
# フィルタリング:特定のドメインへのリクエストのみ
mitmproxy --filter "~d api.example.com"
# トランスペアレントプロキシモードで起動
mitmproxy --mode transparent
# upstreamプロキシとして起動(プロキシチェーン)
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によるフィルタ(regex)
~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 + upstreamプロキシ
強力なシナリオの一つは、mitmproxyを外部プロキシサーバーと組み合わせて使用することです。これにより、トラフィックを同時にインターセプトして分析(mitmproxyを介して)し、外部IPアドレスを介してルーティングすることができます(upstreamプロキシを介して)。この構成は、地理的に依存するAPIのテストや、プロキシを介して動作する必要があるアプリケーションの開発に使用されます。
upstreamプロキシモード
# 全トラフィックをupstream HTTPプロキシを介してルーティング
mitmproxy --mode upstream:http://proxy-host:port
# upstream SOCKS5プロキシ
mitmproxy --mode upstream:socks5://proxy-host:port
# 認証付き
mitmproxy --mode upstream:http://user:password@proxy-host:port
# mitmwebを介してupstreamで
mitmweb --mode upstream:http://proxy-host:port
このモードでは、mitmproxyはローカルでリクエストを受け取り、HTTPSを復号化し、分析や変更を行う機会を提供し、その後外部プロキシを介して転送します。これは、特定の地域からのみアクセス可能なAPIのテストに特に便利です。
このようなタスクには、レジデンシャルプロキシが適しています。これらは、必要な国の家庭ユーザーの実際のIPアドレスを持っているため、地理的に依存するAPIの応答を正しくテストできます。
スクリプト内でのupstreamプロキシの動的選択
# 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:
"""各リクエストのためにupstreamプロキシをローテーションします。"""
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}")
トランスペアレントプロキシ(transparent mode)
トランスペアレントモードでは、アプリケーションはそのトラフィックがインターセプトされていることを知りません — 設定でプロキシを設定する必要がありません。これは、OSレベルで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を使用して解決する具体的なタスクを考えてみましょう。
シナリオ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は必要な地域のデータセンターのプロキシを使用してupstreamモードで起動され、特定の国からのリクエストをシミュレートする迅速かつ信頼性の高い方法です。
# 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はブラウザがページを読み込む際に行うリクエストを理解するのに役立ちます — HTMLのソースコードには見えないXHR/fetchリクエストを含めて。これにより、HTMLをパースするのではなく、サイトのAPIに直接アクセスできます。このようなソリューションを開発する際には、データ収集時のブロックを回避するために、レジデンシャルプロキシを使用してIPをローテーションすることがよくあります。
結論
mitmproxyは、HTTP/HTTPSトラフィックを扱う開発者にとって最も強力なツールの一つです。これは、デバッガー、テスト環境、APIドキュメンテーター、セキュリティ分析ツールの機能を統合しています。三つのインターフェース — コンソール、ウェブ、CLI — は、インタラクティブなデバッグからCI/CDでの自動化まで、すべてのシナリオをカバーしています。
```