Python
iPhone
iOS
PythonDay 16

iOS実機のSSL通信をプロキシによって傍受したり改ざんする方法

More than 1 year has passed since last update.

はじめに

スマートフォンアプリ開発でAPIを介しWeb/APIサーバーとやりとりをする場合、「httpsを使っていれば通信はユーザーにバレない」なんてことはなく、Webアプリでツールを使ってできるのと同じようにユーザーには通信内容の確認や改竄などができます。

そのため、そのことを前提にアプリやサーバーAPIの設計と実装を行わない場合、アプリ利用者によるゲームスコア結果送信の改竄や、ソーシャルゲームにおけるレイドボスなどへのダメージ操作、ECサイトアプリでの購入操作なども可能になってしまいます。

また、最近自分は「無料で音楽聴き放題!! - ネットラジオ」というアプリをリリースしたのですが、このアプリに導入するスタティックリンクライブラリが不明な外部サーバーへ通信していないか、SSLを使用しているつもりがそうでない通信をしてしまっていないかのチェックをするため、自分はmitmproxyというプロキシサーバーのツールを利用しています。

以降の内容はこんな感じです

  • mitmproxyとその導入について
  • 通信内容の確認
  • 通信内容の改竄(リクエストの書き換え、レスポンスの書き換え)

必要なものは次の通りです

  • 有線LAN接続されたMac OSX(有線接続が必要なのはインターネット共有機能のためです)
  • homebrew

mitmproxyとその導入について

mitmproxyはコマンドライン製のプロキシツールです。名前のmitmとはman-in-the-middleの略で通信用語のman-in-the-middle attack(中間者攻撃)などに使われる用語から来ているようです。

中間者攻撃というと過激ですが、自分で開発したアプリに対して検証をする際には便利なツールとなります。

イメージとしては次のようになります。

mitm_2.jpg

Mac OSX搭載の標準設定にあるインターネット共有(OSX自体がWiFiルータになる機能)を使い、通信をproxyで中継しiOS実機でそのWiFiを利用するというのが大雑把な仕組みです。

Pythonのインストール

mitmproxyの実行には、まずPythonが必要になるのでhomebrewでPythonをインストールします

 $ brew update
 $ brew upgrade
 $ brew install python

次にpipをインストールします。easy_insallはbrewによるpythonのインストールと同時にインストールされているはずです。

$ easy_install pip

mitmproxyのインストール

pipからmitmproxyをインストールします

$ pip install mitmproxy

すでにインストールしていてアップデートしたい場合は次のようにオプション-Uを付けてアップデートしておきましょう。

$ pip install mitmproxy -U

pipでインストールすることで関連するライブラリもインストールされます。

mitmproxyの起動

実行は次の通り、オプション-pでポートを指定します。

$ mitmproxy -p 8080

私はポート8080を指定していますが、これはiPhone側からのWiFi設定でも8080を指定が必要です。

iOSのWiFi設定からHTTPプロキシで手動を選択しサーバーのアドレスとポート番号を指定できるようになっています。

IMG_0669.jpg

(図のsunnyとはMac OSXのマシン名で、WiFi名ですので気にしないでください)

iPhone実機へのルート証明書のインストール

またmitmproxyを実行すると~/.mitmproxy/にmitmroxy-ca.pemという証明書が作成されます。
OS XからiPhoneへこの証明書をメール送信し、iPhone側のメールクライアントから証明書を開くことでインストールすることが可能です。なお、この証明書はiOSアプリ開発者にはお馴染み[設定][一般][プロビジョニング]から確認できます。

mitmproxyを使う

mitmproxyの簡単な利用として、リクエストとレスポンスを見たり、それらを改竄、または通信を止めることが出来ます。

リクエストの改竄については、例えばすでに送信したリクエストからパラメータなどを変更することができ、変更したリクエストを再度送ることが出来ます。

名称未設定.jpg

インタラクティブにリクエスト/レスポンスデータを書き換え

mitmproxyを起動した図の画面のリクエスト一覧から、[Enterキー]でGETしたリクエスト/レスポンスの詳細画面に移ります。
[eキー]でリクエスト(クエリ、パス、URL、ヘッダー、form、body、メソッド)を編集、
一覧に戻り[rキー]で編集したリクエストを再度GETすることができます。

また、気付きづらいのですが[Vキー]で編集をもとに戻すこともできます。これらのショートカットは[?キー]を押すことによって確認することができます。

このようにインタラクティブにリクエストを編集できる機能は、サーバー側にAPIコールをお気楽に試せるため、サーバーAPI側の仕様が不明な場合に検証やテストを行うのに最適です。課金アイテムのリクエストなどでパラメータをデタラメにした場合の異常系や、In-App Purchase時のサーバーへのレシートの送信もテストできるでしょう。

逆にこのインタラクティブな書き換えでは、リクエストを編集し再度GET/POSTなどしても、アプリ側の通信が完了済みのため、アプリ側で改ざんされたリクエスト/レスポンスを受けてのテストなどが出来る場合は少ないと思います。

スクリプトによるリアルタイムなデータ改竄

mitmproxyではインタラクティブな操作によるリクエスト/レスポンスの改竄ではなく、あらかじめ改ざんする内容を記載したPythonスクリプトファイルを指定することにより、通信ごとにスクリプトを適用することが出来ます。

例えばソーシャルゲームで多人数のユーザーが共通のボスと戦っているレイドボスと呼ばれるシステムがあったとします。

大抵の場合、流れは次のようになるでしょう

  • ユーザーAがボスに100ダメージ与えた
  • サーバーにユーザーAが100ダメージを与えたことをリクエスト送信
  • サーバー側はユーザーAが100ダメージ与えたと認識
  • ユーザーAに完了のレスポンス

mitmproxyでダメージを変え、ダメージを与えたご褒美アイテムの数を変更することを考えると、次のようになります。

  • ユーザーAがボスに100ダメージ与えた
    • リクエストをフックし9999ダメージ与えたことに変更
  • サーバーにユーザーAが9999ダメージを与えたことをリクエスト送信
  • サーバー側はユーザーAが9999ダメージ与えたと認識
  • ユーザーAに完了のレスポンス
    • レスポンスをフックしご褒美アイテムの数を増やす

スクリプトでリクエストレスポンスの改竄を行うことで、インタラクティブな書き換えではタイムアウトしてしまい出来なかった事ができるようになってしまいます。

スクリプトの指定

$ mitmproxy -s examples/add_header.py

サンプルにあるadd_header.pyは次のようにシンプルな内容なので導入に持ってこいではないでしょうか。

add_header.py
def response(context, flow):
    flow.response.headers["newheader"] = ["foo"]

responseメソッドは通信のレスポンスをHTTP(S)クライアントに届ける前に書き換えるメソッドです。このメソッドをオーバーライドし、レスポンスヘッダーに”newheader”を追加し、値をリストから文字列”foo”としています。

FlowクラスやResponseクラスについてはflow.pyに定義されているため、仕様を理解するにはそこを参照するしかないでしょう。

https://github.com/mitmproxy/mitmproxy/blob/master/libmproxy/flow.py

スクリプトで特定のホストに対するリクエストかどうかの条件判断をするサンプルコードもありこれが一番参考になるでしょう。

https://github.com/mitmproxy/mitmproxy/blob/master/examples/redirect_requests.py

redirect_requests.pyは”example.com"のホストへのリクエスト全てをオリジナルなレスポンスを作成して返し、"example.org”であれば"mitmproxy.org”へとリダイレクトします。

このサンプルコードを元に、自分の用意するホストへのリクエストの改竄を行い検証するのが手っ取り早いでしょう。

mitmdumpについて

mitmproxyをインストールするとmitmdumpというツールもインストールされます。mitmdumpはこれまで説明してきたmitmproxyと違い、結果を先述のコンソール画面のようなインタラクティブなリスト表示ではなく、標準出力へ出力するため、grepと併用するなどし結果を絞り込むことも出来るようです。これを利用することで特定ホストに対してhttpsを使っているつもりが、httpになっているかなどのチェックを効率的に行うことが出来るでしょう。

操作と用途まとめ

  • mitmproxyの基本操作でアプリ内課金で不正なリクエストにしてみる
  • mitmproxyの基本操作でアプリ内課金部分でアプリからニセ(使用済みまたは他のアプリ)のレシートをサーバーに送って何が起こるか簡単に試せる
  • mitmproxyのスクリプトで特定ホストへのリクエスト/レスポンスを改竄してみる
  • mitmdumpで特定ホストに対してhttp通信してしまっていないか確認できる

ここで書かなかったこと

ここに書いた方法はポート8080にアクセスするようにwifiの設定を変更しています。ですが、透過型プロキシを使えば、実機がwifiのデフォルトである80ポートを使って通常と同じように通信をしているようにすることも可能です。

透過化型プロキシを使う方法は下記のサイトを参照すれば良いと思います。

MacでWifi共有で透過的にmitmproxy
http://bagpack.hatenablog.jp/entry/2014/02/04/225553

おわりに

世の中のWeb API設計/運営者やアプリ開発者が以上のことを正しく知っていないと、障害が起きた時の復旧や違反者の追跡など本来のコンテンツ運営とは違う時間がかかってしまいダメージは大きく計画の停滞感さえまねきます。発端はスマートフォンアプリでhttpsでの通信を行えば安心などの楽観的思考により設計が受け身になっていることだと思うので、まず通信の傍受/改竄方法を知ることが先決かなと思いました。

実際、AppleのGameCenterはplistでスコアを送信しているようで、mitmproxyによるインタラクティブなリクエストの改竄だけで、アプリ利用者のスコアを変更できるようです(現在は対策されているかもしれませんが)。

Setting highscores on Apple's GameCenter
http://mitmproxy.org/doc/tutorials/gamecenter.html

具体的なセキュリティ対策に対しては、それぞれのコンテンツの要件と優先順位によって変わると思うので各自で考えましょう。もしくは弊社に相談を頂ければ一緒になって考えたりもします

参考URL

mitmproxy
http://mitmproxy.org/

mitmproxyのスクリプトサンプル
https://github.com/mitmproxy/mitmproxy/tree/master/examples

mitmproxyを使ってSSL通信の中身を確認する
http://ido.nu/kuma/2012/01/29/how-to-use-mitmproxy/

pyasn1 and PyOpenSSLの依存関係があるからpip使うようになった?
https://github.com/mitmproxy/mitmproxy/issues/37