mitmproxyでHTTPSプロキシする時、どんな通信が発生しているのか追ってみる
前回の記事で、ブラウザからアクセスするURLはそのままで接続先サーバだけ変える方法と、その際のHTTP通信の様子を確認しました。今回はその続きで、HTTPSの場合について、ブラウザとmitmproxy間、mitmproxyとサーバ間の通信内容を追っていきたいと思います。
処理の概要
HTTPSプロキシする場合のmitmproxyの挙動について書かれたドキュメントがあるので、そちらを参考にして概要を掴みます。以下はそのドキュメントから引用した図です。
この図に基づいて、順番に説明していきます。
1-2: ブラウザとプロキシ間でHTTP CONNECT
最初にHTTP CONNECT
でブラウザとプロキシ間をつなぎます。通常のプロキシの場合はここでプロキシ先サーバとのTCP接続(HTTPトンネル)を確立するのですが、mitmproxyはしません。
mitmproxyはHTTPSリクエストの確認・変更に対応するため、ブラウザとプロキシ間、プロキシとサーバ間それぞれでTLS接続をします。そのため、ここでHTTPトンネルを作る必要はありません。
3,6: ブラウザとプロキシ間でTLS接続確立
次にブラウザとプロキシ間でTLSハンドシェイクをします。あとでWiresharkで確認しますが、3でClient Hello
、6で残りのやりとりをしています。4-5ではプロキシとサーバ間でTLSハンドシェイクをしています。
なぜこのような順序なのかというと、mitmproxyが独自のサーバ証明書を作成する際、証明書に紐付けるドメイン名を明らかにするためです。この情報は、本物のサーバ証明書のCommon Name
とSubjectAltName
という箇所に書かれています。今回初めて知ったのですが、サーバ証明書には複数のドメイン名を紐付けられるらしいです。実際www.google.co.jp
にアクセスしたときのサーバ証明書を見てみると、Common Name
は*.google.com
で、SubjectAltName
は以下のようなドメイン名の列になっています。*.google.co.jp
やyoutube.com
もここにあります。
これらの情報を取得してからサーバ証明書を作るため、このような実行順序になっているようです。
4-5: プロキシとサーバ間でTLS接続確立
3,6で書いたような目的のために、ここでプロキシとサーバ間でTLS接続を確立します。図にはTLSハンドシェイクをどこまで進めるのか書かれていませんが、実際の挙動としては最後まで進めているようです。
7-8: HTTPSリクエスト
ここまででブラウザとプロキシ間、プロキシとサーバ間それぞれでTLS接続が確立されたので、あとはいつもどおりリクエストを送るだけです。ただし、前回の記事に書いたような方法で接続先サーバを変える場合、プロキシとサーバ間ではもう一度TLS接続が必要になります。接続先変更後のサーバとはTLS接続していないためです。
実際の通信をみてみる
次にWiresharkで実際の通信をみてみます。
今回はブラウザからhttps://example.com/
にアクセスすると、実際はhttps://www.google.co.jp
にアクセスするようにしてみました。mitmproxyは以下のように実行しました。プロキシのポートは8080番です。その他の設定については前回の記事を参考にしてください。
mitmproxy --no-http2 -s "./mitmproxy_replace_host.py example.com www.google.co.jp"
ブラウザからhttps://example.com/
にアクセスすると、TLSとHTTPのレベルでは以下のようなパケットが飛んでいました。
大体ドキュメント通りに並んでいます。8で再びTLS接続をしていますが、これはスクリプトがリクエストを書き換えているためです。4-5ではhttps://example.com/
にTLS接続したのに対し、8ではhttps://www.google.co.jp
にTLS接続しています。
まとめ
HTTPSの場合について、ブラウザとmitmproxy間、mitmproxyとサーバ間の通信内容を確認しました。mitmproxy組込みのCA証明書を使うために、通常のHTTPSプロキシとは異なる手順を踏んでいることがわかりました。
参考にしたページ: http://docs.mitmproxy.org/en/stable/howmitmproxy.html