Help us understand the problem. What is going on with this article?

mitmproxy を使ってアプリの API を無理やり開発環境に向ける

More than 5 years have passed since last update.

mitmproxy とは

mitmproxy は Man in the Middle Attack の要領で HTTP リクエストのを覗いたり改ざんするための HTTP プロキシサーバーです。
mitmproxy が生成する証明書を端末にインストールすることで、HTTPS 通信も同様に扱えます。

mitmproxy のインストール等についての詳細は以下の記事が参考になります。

今回やりたいこと

mitmproxy によるリクエストの改ざんは、Python スクリプトを書くことで行えます。
今回は以下のようなケースを想定して、手順を解説します。

  • Web API へのリクエストを伴うスマートフォンアプリをデバッグしたい
  • そのデバッグには手元の開発環境で動いている Web API を使いたい
  • その開発環境を向いたアプリのビルドを用意することができない

機能ブランチ等から開発環境用のビルド・配布を自動化するなどして、解決することもできるでしょう。
ですが、そういった環境が用意できていないこともあるでしょう。
また、そういう環境があったとしても、複数の環境を何度も切り替えながら確認したいときなどは、複数のアプリを立ち上げなおしたりしてやるよりも、リクエストの改ざんで済ませた方が楽なこともあります。

手順

mitmproxy をインストールする

前述のiOS実機のSSL通信をプロキシによって傍受したり改ざんする方法などを参考にしてください。

リクエスト改ざん用の Python プログラムの用意

今回やりたいのは、「アプリ上で行われる Web API のリクエスト先を開発環境に向けかえたい」ということです。
この場合、以下の 2 点を行う必要があります。

  1. TCP/IP リクエスト対象となるホスト名を改ざんする
  2. HTTP リクエストヘッダ中の Host を改ざんする

これを行うのが以下の Python プログラムです。
ホスト名等は必要に応じて変更してください。

replace_host.py
def request(context, flow):
    # 元のホスト名 (本番環境)
    original = "example.com"
    # 置換後のホスト名 (開発環境)
    replace  = "dev.example.com"

    if original in flow.request.host:
        if original in flow.request.headers["Host"]:
            # リクエスト先ホスト名
            flow.request.host = replace
            # HTTP リクエストヘッダ中の Host
            flow.request.headers["Host"] = [replace]

リクエストヘッダについては、Host 以外でもこれと同様に改ざんが可能です。
このとき注意が必要なのは、flow.request.headers はヘッダ名をキーとして、値には 文字列の配列 が入るということです。
HTTP ヘッダにおいては、同一のキーで複数の値がセットされることがあり得ます。
(Set-Cookie 等)

mitmproxy を起動する

用意した Python スクリプトを読み込ませながら起動します。

$ mitmproxy -p 8080 -s replace_host.py

あとはこのプロキシを使ってアプリをデバッグするのみです。

おまけ: 特定のパスにおいてのみリクエストを改ざんする

先ほどのプログラムでは、「特定のホストへのリクエストのみ」を条件に改ざんを行っていました。
さらに、「特定のパスにおいて」という条件も追加する場合、以下のようになります。

replace_host.py
def request(context, flow):
    # 元のホスト名 (本番環境)
    original = "example.com"
    # 置換後のホスト名 (開発環境)
    replace  = "dev.example.com"
    # 改ざんを行うパス
    target_path = '/api/foo'

    if original in flow.request.host:
        if original in flow.request.headers["Host"]:
            # 特定のパスにおいてのみ
            if flow.request.path == target_path:
                # リクエスト先ホスト名
                flow.request.host = replace
                # HTTP リクエストヘッダ中の Host
                flow.request.headers["Host"] = [replace]

flow.request.path の値をチェックすることで実現できます。
ここでは == による完全一致をチェックしていますが、正規表現で特定のディレクトリ以下全てを対象にするなど、様々な応用が可能でしょう。

mitmproxy の公式ドキュメントにはこの辺のレシピ集的なものが集まってれば便利なのになー、と思ったり。

yuya_takeyama
Ruby / PHP / JavaScript / Golang
http://yuyat.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした