Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
513
Help us understand the problem. What is going on with this article?
@hkurokawa

モバイルアプリ開発者のための mitmproxy 入門

More than 5 years have passed since last update.

はじめに

モバイルアプリを開発しているときに、アプリとサーバー間の通信を確認したいときがあります。たとえば、期待通りの HTTP リクエストが送られているか調べたり、サーバーからのレスポンスが間違っていないか確認したりする必要が生じます。

そんなときに、いちいちデバッガで止めても良いのですが、プロキシをはさめば簡単に通信を覗くことができます。しかも、レスポンスを改竄して、わざと不正なレスポンスにしてアプリがクラッシュしないかテストしたり、特定のリクエストだけブロックしてサーバー障害を擬似的に再現することができます。

mitmproxy とは

mitmproxy は man-in-the-middle 型のプロキシサーバーのツールです。OS X や Windows、Linux 上で動作し、対話式の CUI を持ちます。SSL サポートをしている点が特長になります。

公式サイト: http://mitmproxy.org/doc/mitmproxy.html

インストール

以下では、mitmproxy の導入方法について説明します。

マシン側での準備

まず mitmproxy を手元のマシンに導入します。pip を使ってインストールするのが一番簡単でしょう。pip についてはここを参照してください。

pip install mitmproxy

でインストールできます。

なお、お使いのマシンが OSX の場合はインストールガイドにもあるように、XCode が必要なようです。

モバイル端末の準備

次に、モバイル端末を設定して、手元のマシンで動いている mitmproxy をプロキシサーバーとして使うようにします。

iOS

iOS の場合は、「設定」→「Wi-fi」に行って、いま接続しているネットワークをタップ → 「HTTPプロキシ」でプロキシの設定ができます。設定項目には以下を入力します。なお、ポート番号はデフォルトで 8080 ですが、後述のようにプロキシサーバー起動時に別の番号を指定可能です。

  • サーバー:自分のマシンの IP アドレス
  • ポート番号:8080
  • 認証:オフ

IMG_3019_filtered_s.png IMG_0635_filtered_s.png

Android

Android の場合は、「設定」→「Wi-Fi」に行って、いま接続しているネットワークを長押し →「ネットワークを変更」をタップして、開いたダイアログの「詳細オプションを表示」のチェックボックスにチェックを入れるとプロキシ設定の入力欄が表示されます。設定項目については、iOS と同じなので省略します。
Screenshot_2014-08-31-12-24-23_filtered_s.png Screenshot_2014-08-31-12-24-51_filtered_s.png

証明書のインストール

さて、まずは mdotmproxy を起動してみましょう。デフォルトではポート番号8080で起動しますが、mitmproxy -p 8081 のように -p オプションを指定することで別の番号を指定可能です。

% mitmproxy

この時点で、モバイル端末でネットワークに繋ごうとすると、上記のプロキシを通るはずです。しかし、大抵はアプリ側で通信エラーが起きます。これは、端末に証明書が設定されていないために起こるものです。そこで、モバイル端末上のブラウザを開き http://mitm.it というアドレスにアクセスします。そうすると以下のような画面が表示されます。
51e899bf-320d-e06b-8fe1-ebde803ad16a_m.png

ここで、Android なら Android、iOS なら iOS のアイコンをタップすることで証明書をインストールすることができます。

基本的な使い方

それでは、さきほど起動した mitmproxy の画面を見てみましょう。モバイル端末でなにか操作をすると、行われた HTTP 通信の一覧が出力されるはずです。たとえば、以下は Play Newsstand を起動したときのものです。なお、Shift + Fを入力するとfollowingモードになって自動的に最新のリクエストが最下段に表示されます。

また、Eキーを押すと、画面が2段になって下段にイベントログが表示されます。
terminal.png

もし1つ1つの通信内容を見たいのであれば、もう一度Shift + Fを入力してfollowingモードを解除してから、JキーおよびKキーで上下にカーソルを動かして見たい通信を選びます。通信を選んでEnterキーで内容が見られます。

リクエストとレスポンスはTABキーで切り替えられます。また、先程の通信リストに戻るにはQキーを押します。

reqresp.png

フィルタリング

このように、モバイル端末上のすべての通信を見ることができますが、通常は量が多すぎて目的の通信を探すのが大変です。mitmproxy 上でLキーを入力することで、フィルタリングすることが可能です。正規表現パターンに合致した URL で絞り込むこともできますし、ヘッダやメソッドで絞り込むこともできます。

以下の例では、play.googleapis というパターンで絞り込んだところです。
filtering.png

インタセプト

また、特定の通信を止めて中身を書き変えるということもできます。mitmproxy 上でIキーを入力してからインタセプトしたい通信に合致するパターンを入力してください。使えるパターンはフィルタリングと同じです。

その状態で通信を行うと、下のようにインタセプトされた通信がオレンジで表示されます。
intercept_01.png

インタセプトした通信を書き変えるには、インタセプトした状態でEnterキーで通信を表示し、Eキーを入力します。
intercept_04.png

この状態で通信を書き変えてから、Qキーで先程の画面に戻りAキーを押すと通信が行われます。

書き変えはリクエスト時とレスポンス時の2回行えます。最初にAキーを押すとリクエストが許可され、レスポンスが返ってきたところでまたインタセプトされます。そこでさらにレスポンスを編集してからAキーを入力すると、改竄されたレスポンスが端末に届きます。
intercept_02.png

intercept_03.png

もし複数の通信が同時にインタセプトされて、すべて通したい場合はShift + Aを入力します。

高度な使い方

mitmproxy には、さらに高度な使い方があります。ここではクライアントサイド・リプレイとスクリプトによる書き変えを紹介します。

クライアントサイド・リプレイ

クライアントサイド・リプレイは、一度行った通信を再生する機能です。このとき、リクエストの内容を書き変えることができます。

mitmproxy の公式サイトでは、例として Apple Game Center のゲームのハイスコアを改竄する方法が載っています(一時、ランキングがすべて 2,147,483,647 すなわち 2^31 - 1 になっていたことがあるそうです)。

リプレイを行うには、再生したい通信の場所までカーソルを持ってきてRキーを押します。もし、事前に編集したい場合は、先にEnterキーで通信を表示し、Eキーで編集します。編集が終わったら、Qキーで抜けて、先程と同じようにRキーを押します。

リプレイが実行されると、通信の先頭に以下のようなリプレイ・マークが付きます。
replay.png

スクリプティング

通信を改竄する簡単な方法はインタセプト機能を使う方法ですが、いちいちリクエストやレスポンスを手動で書き変えるのは面倒ですし、編集している間に通信がタイムアウトしてしまうこともあります。そういうときは、スクリプティング機能を利用すると良いでしょう。

たとえば、とあるニュース・アプリが、記事リスト更新時に /api/v1/list.json?type=reload という REST API を利用しているとします。このアプリの通信を改竄して、サーバーからのレスポンス内容を機械的に置換したいとしましょう。そのようなときは、以下のスクリプトをtest_mitmproxy.pyという名前で保存して、-sオプション付きで mitmproxy を起動します。

# -*- coding: utf-8 -*-
def response(context, flow):
    path = flow.request.path
    query = flow.request.get_query()
    if path.endswith("/api/v1/list.json") and query.in_any('type', 'reload'):
        flow.response.replace("艦これ", "hogehoge")
mitmproxy -s test_mitmproxy.py

この状態で、アプリを起動して該当の API 呼び出しを発生させる(たとえば、記事リストをリロードする)とレスポンス中の「艦これ」がすべて「hogehoge」に書き変わっているはずです。

また、スクリプト中では、引数を取ることができるようになっています。たとえば、以下のスクリプトは通信をランダムに切断することで擬似的に不安定な通信環境を再現するものです。

# -*- coding: utf-8 -*-
import random

def start(ctx, argv):
    if len(argv) != 3:
        raise ValueError('Usage: -s "pseudo_unstable_network.py hostname percentage(0-10)"')
    # You may want to use Python's argparse for more sophisticated argument parsing.
    ctx.arg1, ctx.arg2 = argv[1], argv[2]

def request(context, flow):
    host = flow.request.host
    if host.endswith(context.arg1) and random.randint(0, 10) <= int(context.arg2):
        context.log("Kill the request for %s. Host: %s" % (flow.request.get_url(), host))
        flow.kill(context._master)

このスクリプトを指定するときは、以下のように引数を指定します。この例では api.example.com との通信を 80% の確率で切断します。

mitmproxy -s "pseudo_unstable_network.py api.example.com 8"

おわりに

いかがでしたでしょうか。mitimproxy を使うと、このように簡単に通信内容を覗いたり書き変えを行うことができます。

みなさんも、mitmproxy を利用して、ぜひ快適なモバイルアプリ開発生活をお送りください。

513
Help us understand the problem. What is going on with this article?
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
hkurokawa
Android Engineer

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
513
Help us understand the problem. What is going on with this article?