mitmproxyを使って、アクセスするURLはそのままで接続先サーバだけ変える

最近App Engineでの開発中に、ブラウザからはexample-a.comというURLでアクセスしたいけど、実際にはexample-b.comのサーバに接続させたい、ということがありました。こういうとき、mitmproxyというツールを使うと楽だったので、やり方をメモしておきます。ついでに、mitmproxyをプロキシとして使う際の通信の様子をちょっと追ってみます。

mitmproxyについて

mitmproxyはHTTPプロキシの一種で、これをブラウザのプロキシに設定しておけば、HTTPトラフィックの確認や変更が手軽にできます。特に、mitmproxy組込みのCA証明書をインストールしておけばHTTPS通信も簡単に変更できちゃうところが良いです。

今回は、ブラウザからはexample-a.comドメインにアクセスしているように見えるけど、実際はexample-b.comにアクセスしている、という状況を作ります。ブラウザ、mitmproxy、サーバは、以下のような働きをします。

  • ブラウザ: mitmproxyをプロキシとしてHTTPリクエストを出す
  • mitmproxy: HTTPリクエストを書き換え、ブラウザが指定したサーバとは異なるサーバへHTTPリクエストを出す
  • サーバ: 特に変更なし

準備

  1. ブラウザのProxy設定

    mitmproxyはデフォルトで8080番ポートをLISTENしますので、ブラウザのProxy設定を127.0.0.1:8080にします。

  2. mitmproxyの準備

    mitmproxyを http://docs.mitmproxy.org/en/stable/install.html にある手順に沿ってインストールします。 MacOSの場合はbrewで一発です。

    brew install mitmproxy

    次に、proxy組込みのCA証明書を http://docs.mitmproxy.org/en/stable/certinstall.html にある手順に沿ってインストールします。

  3. HTTPリクエスト/レスポンスを書き換えるスクリプトを作成

    以下のスクリプトをダウンロードしておきます。

    Replacerクラスのrequestメソッドで、送られてきたHTTPリクエストのターゲットとHost Headerを書き換えています。また、リダイレクトが返ってきてもある程度はうまく動くようにHTTPレスポンスのLocationも書き換えています。

リクエストを書き換えてみる

実際にHTTPリクエストを書き換えてみます。まずは以下のようにしてmitmproxyを実行します。[src domain name]にブラウザからアクセスするドメイン名、[dst domain name]に実際に接続させたいサーバのドメイン名を指定します。

mitmproxy --no-http2 -s "./mitmproxy_replace_host.py [src domain name] [dst domain name]"

--no-http2オプションを指定しているのは、HTTP2のリクエストだと、HTTPリクエストのターゲットを書き換える箇所でエラーが出るためです。

この状態でブラウザからサイトにアクセスすると、[dst domain name]で指定したサーバの方にリクエストが飛んでいることが確認できるかと思います。

mitmproxyはどんな通信しているのか?

ここまでで目的は果たせましたが、せっかくなのでもう少し掘ってみます。具体的には以下の2点です。

  • HTTPの場合、mitmproxyはブラウザ、サーバとどんな通信をするのか?
  • HTTPSの場合はどうなのか?

まずはHTTPの場合です。ここの通信の様子はmitmproxy自身では確認できないと思うので、Wiresharkで見てみます。

今回はブラウザからhttp://ks888.hatenablog.com/にアクセスすると、実際はhttp://localhost/にアクセスするようにしてみました。localhostの80番でモックサーバーを起動して、mitmproxyを以下のように実行しました。

mitmproxy --no-http2 -s "./mitmproxy_replace_host.py ks888.hatenablog.com localhost"

ブラウザからhttp://ks888.hatenablog.com/にアクセスすると、HTTPレベルでは以下のようなパケットが飛んでいました。

f:id:ks888:20170504192518p:plain

赤枠の箇所にHTTPリクエスト/レスポンスの一行目が表示されています。ブラウザからプロキシへのHTTPリクエストは以下のようになっています。

GET http://ks888.hatenablog.com/ HTTP/1.1

注目したいのはhttp://ks888.hatenablog.com/で、いつもはURLのパス部分だけ指定されている箇所にプロトコルドメイン名まで指定されています。これはブラウザがプロキシ宛にHTTPリクエストを出す際、本当に接続したいサーバー(=オリジンサーバー)を指定する方法です。HTTP/1.1のRFCにもこのあたりに記載されています。

今回のスクリプトではここの値を他の値にすることで、リクエストをブラウザで指定したものとは異なるサーバに飛ばしています。

プロキシからサーバへのHTTPリクエストは以下のようになっています。特に気になるところはないです。

GET / HTTP/1.1 

HTTPレスポンスについても特に気になるところはありませんでした。

簡単ですがHTTPの場合を見てみました。次にHTTPSの場合を見ていきますが、長くなってきたので次回の記事に回したいと思います。TLSハンドシェイクをブラウザ<->プロキシ間とプロキシ<->サーバー間でやるだけでは?と思ってましたが、実際にはもう一工夫必要そうでした。

追記:続きの記事を書きました。

まとめ

mitmproxyを使って、ブラウザからはexample-a.comドメインにアクセスしているように見えるけど、実際はexample-b.comにアクセスしている状況を作りました。また、HTTPの場合にmitmproxyはどんな通信をするのか見てみました。

参考にしたページ: http://docs.mitmproxy.org/en/stable/howmitmproxy.html