2011-01
23
02:40:40
続: 楽天銀行アプリのセキュリティについて – プロトコルを解析してみた


結論から書き出すと、結局のところ楽天銀行アプリはUDIDを通常(初期)ログインとクイックログインそれぞれのタイミングでサーバー送出をしていた。この事実からは楽天CERTの説明には不信がある。
さてどうしてくれようというのが現時点な訳だが、まずはどういう仕様で楽天銀行アプリが動作しているのか明らかにしてみよう。楽天CERT担当者からは「即座に悪用できるものではない」との見解ももらっているしまたそもそもリバースエンジニアリングしている訳でもなく公共のインターネットを飛び交っているデータプロトコルについての解説なのだから誰に謀るものでもないだろう。ということで、安心して内容について考察してみる。

楽天銀行アプリ調査のその後

当初「どうやって調査続行したものやら」と迷っていたのだが、高木浩光さんから「ヘルプの「ライセンス情報」に「SFHFKeychainUtils」という記載があるよ」と教えてもらった。このSFHFKeychainUtilsというのはiOS開発で使用されるkeychainアクセスのためのライブラリだ。
僕はアプリケーションエリア外部に何か情報を保管している可能性には気付いていたもののその手法に思いついていなかった。
まさしくビンゴで、keychainを覗くと「REGIST_CODE」と「UDID」というまさしくそのままのフィールド名が確認できた。もちろん名前通りのフィールドである保証もないものの、何らかUDIDなどに関わる情報が端末ローカルに保存されている可能性が出てきた訳だ。
しかしkeychainへのアクセス調査は難度が高い。 keychainファイル自体は単なるSQLite DBでしかなく中身もツールで確認できるのだが、データ自体はiOSが暗号化している(しかもiOS3と4とでは暗号化方式も変わっているようだ)。そのためどのようなデータが保管されているかは判明せず、結局のところデータアクセスタイミングについて少し理解できたに止まった、

そこで方針を変えて、当初「SSLだから」と諦めかけていたのだが、パケットキャプチャで仕様を明白にすることにした。
少し手間取りはしたが何とかパケットが拾えるようになったのだが、結果まず驚いたのはまさしくあれだけ楽天CERTへの質問の焦点にしていたUDIDは実は躊躇無く送信されていた事実だった。またその他の動作仕様も大変興味深いものだ。
なお別に僕はセキュリティの専門家ではないので細かくは説明しにくいが、もし公表することに問題が無いのであれば非常に実用的な手法でもあるので後日方法については別途エントリーにしてみよう。
(念のため、当たり前だが別にSSLの暗号を破っているのではなく、恐らく専門家であれば常識的な方法だろう。所謂MIM手法である)

楽天銀行アプリの動作仕様  クイックログイン設定まで

おおまかに説明すると、初期状態〜クイックログイン設定を行うまでの前半とクイックログインをして認証しアプリを利用する後半から成り立っている。
なおパケットをキャプチャしたからと言って特段すべての開発者の意図が分かった訳でもない。以下はそのキャプチャ結果からの僕個人の見解に過ぎないことを最初に明示しておく。

さて、まず初回起動時などまだクイックログイン設定されていない場合には二度サーバーへのアクセスがされる。
一度目は単に設定ファイルを取得しているだけで内容も大したことは無い。
二度目は恐らくクイックログインが有効かどうかサーバーへ確認するフェーズだ。
先に述べたとおり、ローカルには「REGIST_CODE」と「UDID」というデータを保持しているようだ。想像でしかないが、恐らくREGIST_CODEとはクイックログインなどを行った後に発行される認証を確認するためのトークンのようなものだろう(後述)。またUDIDは単に20バイトのデータを格納しているにしては暗号化後のデータサイズが大きすぎた。UDIDも当然格納されているかも知れないが、他のデータも格納されている可能性がある。
この二度目では次のようなデータがPOSTされ返答されている。

(リクエスト)
VER=1_0_2&CMD=SMP_CMD_0001&CID=SMP_CID_0001&TKN=&TMID=7015999cfcd1b-e41a-41bebf-4a50f0-77c7a71365e4d3dab80

(レスポンス)
1_0_2<sep/>00000<sep/><sep/>1295682263804<sep/>85725ea72df41ff384e08a7d31fdfcbe<sep/><sep/>2<sep/><sep/><sep/>

細かくは想像にしかならないが、リクエストパラメータとしてはプロトコルまたはアプリのバージョン番号(VER)、コマンド(CMD)、画面遷移番号(CID)だろう。この画面遷移番号はこの後のリクエストで徐々にインクリメントされる。
TKNは恐らくトークンだ。ここでは空値だがこの後リクエストごとに異なったトークンがレスポンスされ、次回リクエストではそのトークンを用いる仕様になっているようだ。
実は楽天銀行のPCサイトでもそうなのだが、ありがちな「ナビゲーションボタンで前画面に戻るとエラーになるサイト」ではよくある作りだ。
別にCookieも発行されているのでCookieでも十分代用可能と思うのだが何故Cookieで無いのかはよく分からない。
次にTMIDだが、これが先のREGIST_CODEの正体ではないかと思っている。後述になるがこのTMIDはクイックログインの度にリフレッシュされる。
つまりこれでアカウントを紐付けて確認している・・・ように当初見えたのだが、これはまた後述する。
次にログインメールアドレスとパスワードでまず初期ログインを行う。

(リクエスト)VER=1_0_2&CMD=SMP_CMD_0002&CID=SMP_CID_0002&TKN=85725ea72df41ff384e08a7d31fdfcbe&USEID=username&PASSWD=password&UDID=f1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx25

(レスポンス)1_0_2<sep/>00000<sep/><sep/>1295682285455<sep/>a25b63ade1fec4265a0585cd6ee340c7<sep/>kCPdN6KXFsZJ3NRzQ0qmGjFKVncthGpsgJl<sep/>支店番号<sep/>口座番号<sep/>ユーザー氏名<sep/>1<sep/>支店名<sep/>20110122164231

ここでまずUDIDが送られている。
またレスポンスとして氏名や口座番号までが取得されている。ただ疑問なのは、実はこれら氏名や口座情報などは実はクイックログイン設定が終わるまでユーザーに表示されることは無いのだ。
果たして何のために取得しているのか、についても後述にする。

次にワンタイムパスワードを要求する。

(リクエスト)VER=1_0_2&CMD=SMP_CMD_0003&CID=SMP_CID_0003&TKN=a25b63ade1fec4265a0585cd6ee340c7&BCHNO=支店番号&ACNO=口座番号

(レスポンス)1_0_2<sep/>00000<sep/><sep/>1295682294547<sep/>a92c5e6fa5f9a68025af8235fa2685a8<sep/>kCPdN6KXFsZJ3NRzQ0qmGjFKVncthGpsgJl<sep/>1<sep/>ユーザーのメールアドレス

お分かりだろうか。例えばログイン時にトークンなどを取得してそれを返している訳ではない。取得した支店番号・口座番号に対してワンタイムパスワードを要求しているのである。
何度も言うがトークンも使えればCookieもあるのに何故か支店番号と口座番号をアプリから送らせているのだ。
控えめに言っても賢明な仕様とは言えないだろう。

ワンタイムパスワードがメールで届いたらワンタイムパスワードの入力を行う。

(リクエスト)VER=1_0_2&CMD=SMP_CMD_0004&CID=SMP_CID_0004&TKN=43da0d7f18d4d84da178831b355a7d34&BCHNO=支店番号&ACNO=口座番号&SQCD=ワンタイムパスワード

(レスポンス)1_0_2<sep/>00000<sep/><sep/>1295684013098<sep/>010b575c058b27f1e7594e9c20b18d8f<sep/>MsQpN6RGXs2kGffbqYl5V7v7cDyJYnTGDtK<sep/>45f6a0c01a8f1-c2a0-40b6f8-78a472-a2c52c7cbef4c5acd64

実は試行錯誤していた際に気付いたのだが、このワンタイムパスワードは必ずしもリクエストした端末から入力しないといけないことはない。他端末からでも有効なのだ。つまり上記の通り、恐らくはサーバー側ではトークンなどの管理ではなくて、単に口座情報とだけワンタイムパスワードは紐付いているのだろう。だからこのタイミングで支店番号・口座番号の送信が必要になるのだ。
レスポンスにある「45f6a0c01a8f1-c2a0-40b6f8-78a472-a2c52c7cbef4c5acd64」 が先に出たTMIDである。つまりこのIDの発行によってその端末はサーバー側では認証された扱いになる。
そして最初に述べたとおり、アプリ起動時にサーバーへ送信してクイックログイン設定済みかどうかを確認することになるようだ。

楽天銀行アプリの動作仕様  クイックログイン設定後

一度アプリを終了して再起動するとクイックログインを行う画面で今度は起動する。以下はその前段階である。
先にも記載したが、今度はクイックログインが設定できている場合の例となる。

(リクエスト)VER=1_0_2&CMD=SMP_CMD_0001&CID=SMP_CID_0001&TKN=&TMID=45f6a0c01a8f1-c2a0-40b6f8-78a472-a2c52c7cbef4c5acd64

(レスポンス)1_0_2<sep/>00000<sep/><sep/>1295684226752<sep/>1e499034eaa3ac81231ec6a4accfcd35<sep/><sep/>1<sep/>支店番号<sep/>口座番号<sep/>.I.y…x.X

レスポンスでは先ほど取得したTMID(恐らくREGIST_CODEと同じ)が送信されている。またレスポンスでは今度は支店番号と口座番号が返ってきている。今後のアクセスの元になるようだ。
次にクイックログインを行う。

(リクエスト)VER=1_0_2&CMD=SMP_CMD_0005&CID=SMP_CID_0005&TKN=1e499034eaa3ac81231ec6a4accfcd35&BCHNO=支店番号&ACNO=口座番号&PASSWD=XXXX&UDID=f1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx25

(レスポンス)1_0_2<sep/>00000<sep/><sep/>1295684238536<sep/>3ba6527a70feb19644a7dd2286f34c26<sep/>KJQvN6SCHLHr7qcJQNrG5kK690k3b5Q0KT2<sep/>支店番号<sep/>口座番号<sep/>ユーザー氏名<sep/>前回ログイン時刻<sep/>支店名

ご覧の通りリクエストでは常にUDIDが送信されている。
また先ほど取得した支店番号・口座番号も送信されている。そもそもTMIDがあるのだから、アプリへ通知する必要はどこかのタイミングで必要だとしても、ここで送信を行わなければならない必要性が分からない。

次に入出金照会へ遷移する。

(リクエスト)VER=1_0_2&CMD=SMP_CMD_0011&CID=SMP_CID_0011&TKN=3ba6527a70feb19644a7dd2286f34c26&BCHNO=支店番号&ACNO=口座番号

(レスポンス)略

ここでも送信されているのは支店番号・口座番号だ。しかも他に認証単位になりそうなのは他にトークンぐらいしかない。となると一つの疑惑を抱かざるを得ない。つまり「認証はアプリから送られる支店番号と口座番号だけで行っているのではないか」という疑惑だ。
これは仮説に過ぎないが、もし事実だとしたらかなりひどい仕様だと言わざるを得ない。これが金融機関が公式に提供しているアプリなのだから。

最後に端末側でUDIDを偽装した場合の起動時処理を示そう。

(リクエスト)VVER=1_0_2&CMD=SMP_CMD_0001&CID=SMP_CID_0001&TKN=&TMID=

(レスポンス)1_0_2<sep/>00000<sep/><sep/>1295689207525<sep/>13c38c7da33f05ca7a682a7b160e42bf<sep/><sep/>2<sep/><sep/><sep/>

ご覧の通りTMIDが空になっている。これは恐らくローカルに保存しているUDIDと比較した結果異なるのでTMIDを送信していないのではないだろうか。
ただそもそもそうであればリクエストをここで行う必然性はないし、また僕はもっと複雑なチェックを想像していたのだがこれが楽天CERTのいう「とあるチェック」だとしたら何とも単純な話だ。確かにiOSのkeychainの壁は厚いのだが、それさえ乗り越えれば簡単に偽装可能になってしまうからだ。
もしくは初回ログイン時にUDIDはもう送られているのでサーバーで保持しておけば比較可能にはなるが、もしそれだと明らかに当初僕が「UDIDはログインメールアドレスと紐付いているのではないか」と尋ね楽天CERT担当者の「UDIDはログインメールアドレスとの紐付けには使用していない。ログインとは別の要件で使用している。改竄されることも折込済みでとあるチェックで利用しているにすぎない」 という回答と矛盾することになる。

果たしてUDIDは何に使用されているのだろう。ここから先はサーバーサイドのDB紐付けや処理になるので分かりかねるが、仮に「UDIDはただ送っているだけでサーバーでは一切保持も利用もしていない」のだとしてもプライバシー上は懸念される事態だろう。もしもユーザーアカウントに紐付けていたらAppStoreガイドライン違反だし、しかもUDIDを送信することはWEBサイト含めて一切ユーザーには告知されていない。
これは顧客との重要な信義則違反である。

今後について

簡単にまとめてみたが、実はこれだけではない。
例えばWEBサイトなどではこのアプリは特定の端末でしか使用できないとされているが仕様に抜けがあり、実は複数端末で同時にクイックログインが可能に出来る(但し制限あり)ことも確認している。
個人的な感想としては、これが金融機関が提供するアプリとは考えられない作りだ。トークンと言った「無駄な」処理を含むくせに重要なプライバシー・セキュリティ情報である具体的な口座情報を端末で保持している疑いが濃厚であるなど。実にへたくそな仕様定義だと感じられる本職のアプリケーション・エンジニア、SEの方も多くおられるのではないだろうか。少なくとも「ランダムで推測不可能なセッションID」をベースにしたセッション管理の雰囲気が全く無いのが大きな特徴と言えるだろう。

楽天CERTとのやり取りについては先方からの「シャットアウト」で終わった形になっているのだが、今回の事実確認をベースに再度コンタクトしてみるつもりだ。
その反応如何で、果たして楽天CERTが、ひいては楽天銀行のみならず楽天グループ全体がセキュリティやプライバシーへの配慮、顧客保護や社会責任に対してどのような考えを持っているか明らかになることだろう。
何か動きがあればまたご報告したいと思う。

コメントを残す

メールアドレスが公開されることはありません。

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください