雑記

2000|01|
2003|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|
2007|01|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|09|11|
2009|02|03|05|06|07|08|10|11|12|
2010|01|03|04|05|06|07|08|09|10|
2011|05|06|09|10|
2012|03|07|09|12|
2013|01|02|04|05|07|08|10|11|
2014|04|05|08|10|12|
2015|01|05|
2016|09|

2010-06-23 [長年日記]

[Rails] 「Ruby on Rails 携帯サイト開発技法」第9章のサンプルコードに含まれる脆弱性について

.tright small.(2010/6/25公開)^J2010/6/27: 「追記1」記載、微修正^J2010/6/28: 脚注*5 修正

まとめ

ソフトバンククリエイティブ社から発行されている「Ruby on Rails 携帯サイト開発技法」の第9章に掲載されているサンプルコード(ファイル4/4)にはセッションIDの発行・管理に不備があるため、セッションハイジャックが可能です。

解説

この本の第9章には「携帯端末特化型セッション管理」と称していわゆる「かんたんログイン」の実装サンプルが掲載されています。第9章の冒頭部分から、その特長に関する記述を抜粋すると、

  • Cookieの代わりにリクエストヘッダからの情報を使ってセッション管理を行うという、携帯サイトならではのセッションの実装
  • セッションIDを端末識別情報から生成
  • 端末に対するセッションIDは常に固定され、サーバー側がセッション情報を保持してさえいれば、ログイン状態は常に保たれたまま
  • ユーザーからすればログインと言うわずらわしい操作が不要になる
  • 利便性は大きく向上するのですが、これではセキュリティ性をあまりにも下げすぎてしまうので、重要な操作にはパスワードの入力を求めるなどの一定の防護措置を講じる必要はあります

というものだそうです。最後の項目にいくばくかの配慮を感じられますが、残念ながらこの「一定の防護措置」についての具体的な記述は本文やサンプルコードにはありません。

さておき、このサンプルコードを動かすと、

  • ユーザー登録(画面はSoftbank携帯電話のスクリーンショット)^J
  • .clear. いわゆるかんたんログイン^J
  • .clear. プロフィール編集^J
  • .clear. ログアウト(トップページに戻る)

といった機能を持つサイトができます。

さて、このサイトにPCからアクセスするとそのままではユーザー登録もログインできません(画面はChrome)。^J

.clear. ユーザー登録できないのは、上の画面にもある通りPCからのアクセスでは端末IDが取得できないためです。このあたりの処理については解説が274ページにあり、アクセス元のIPアドレスをチェックして、キャリアに応じたリクエストヘッダから契約者IDを取得するという処理で、一般的な偽装対策やPCからのアクセス対策はなされているように見えます。

ログインでも同様なチェックを経てセッションIDを発行しています(解説はp.267)。が、最終的に生成されるセッションIDは以下のreturn文で返される文字列となっています。

# セッションIDを生成する
# ※名前空間が被らないようにプレフィクスを付与しておく
return "!"+carrier.to_s+":"+Digest::MD5.hexdigest(mobile_id)

セッションIDの生成に使われている情報は、"!"および":"という2つの文字とアクセスしてきた携帯のキャリア名、そして契約者固有ID(のハッシュ)だけです。契約者固有IDについては、同書p.258に次のような説明があります。

au、Softbankどちらの機種も、ユーザーに確認ダイアログを出すことなく携帯端末の識別情報を送信する仕組みがあったのですが、ドコモ機種だけはそれがなかったのです。
 そして2008年3月31日、NTTドコモはついにその問題の解決のために「iモードID」をリリースしました。iモードIDは携帯端末を識別可能なIDであり、クエリパラメータにguid=ONとつけておくと、ユーザーに確認ダイアログを出すことなくリクエストヘッダで送られてきます。

ここで今回の話において重要なのは、NTTドコモの機種であればリンクに「guid=ON」と付けてユーザーを誘導するだけで誰でも簡単にiモードIDを取得することができ、au、Softbankにも同様の仕組みがある、という点です。つまり、

.orangered bold.このサンプルプログラムで使用されているセッションIDはユーザーを識別可能、かつ誰でも入手可能な情報のみを元に生成されているため、他人のセッションIDを容易に推測・再現可能である

と言えます。他人のセッションIDを再現可能であれば、あとはそのセッションIDを偽装してサイトにアクセスすることができればハイジャックが成立します。というわけで、サンプルにおいてセッションIDを偽装したアクセスが可能かどうかを次に考えます。

まず、携帯電話からのアクセスについてですが、フィーチャーフォンとスマートフォンに分け、まずはフィーチャーフォンについて考えます。上にも書いた通り、サンプルではアクセス元のIPアドレスをチェックしてキャリアを判定したのち、そのキャリアに応じたリクエストヘッダ(契約者固有ID)から携帯端末識別情報を取得するという処理になっています。従って、キャリアのゲートウェイを通過してきてなおかつ契約者固有IDを偽装できなければ、セッションハイジャックは成立しません。後述する仕様から、厳密に言うとある特定の条件を満たせば成立する余地はありますが、一般的にはフィーチャーフォンからのアクセスではセッションハイジャックは出来ないといって良いと思います。

次に、スマートフォンとPCからのアクセスについて考えます。どうしてこういうくくりになるかというと、スマートフォンにはキャリアのゲートウェイを利用しない経路からのアクセスが可能であり、その場合はこれ以降の説明に該当するためです。まず、このサンプルは、

(キャリア判別処理や携帯端末識別情報取得に)失敗した場合は、デフォルトのセッションロード処理に制御を戻す

と言う仕様になっています(p. 265)。つまり、キャリアのゲートウェイを介したアクセスに対しては、先ほど引用した推測可能なセッションIDが発行され、PC等からのアクセスに対しては、通常のRailsのセッション管理処理が使われるということになります。さてお立会い、ここでPCブラウザから、携帯電話からのアクセス時に発行されるセッションIDを偽装してアクセスするとどうなるでしょう…

.clear. というわけで、携帯電話からログインしていたセッションのハイジャックができてしまいました。以下、各画像で順に何をやったか解説します。 .flat.

下準備
1枚目
携帯電話でサンプルサイトにアクセスし、ユーザー登録をすませてログインします。
2枚目
3枚目
Cookieを設定した後にもう一度トップページにアクセスし、「ログイン」リンクをクリックすると、ログインに成功して先ほど携帯から登録した名前が表示されています。
4枚目
プロフィールも携帯から設定したものであることが確認できます。
5枚目
プロフィールを編集してみます。
6枚目
携帯電話でプロフィール画面に飛ぶと、プロフィールが改ざんされていることが確認できました。

つまり、このサンプルの方法を採用したサイトに関しては、攻撃者視点で書くと、

  1. あらかじめ他のサイト等をつかって、被害者の契約者固有IDを調べておく
  2. 攻撃対象となるサイトにアクセスして、セッションIDが保存されているCookieの名前を調べる
  3. 偽装したセッションIDを使ってアクセスする

という手順で、他人に成りすましてサービスを利用することが出来てしまいます。成りすましに必要なすべての情報がサイトに侵入するなどの攻撃的な手段を用いずとも収集可能である、という点が重要です。

セッションIDを推測困難にする

契約者固有IDの利用の是非はさておき、今回指摘した脆弱性の根本的な問題点は、

セッションIDとして容易に推測・再現可能な値を利用した

という点にあります。この点だけを解決するもっとも安直な方法は、携帯電話で発行するセッションIDを

return "!"+carrier.to_s+":"+Digest::MD5.hexdigest(mobile_id+"ランダムな秘密の文字列")

とすることです。"ランダムな秘密の文字列"の部分には"ランダムな秘密の文字列"と書くのではなく、サイト管理者が適当にランダムな秘密の文字列を生成して、それを書きます。この"ランダムな秘密の文字列"は第三者に知られてはなりませんし、単純であったり、短かったりしてブルートフォースアタックで簡単に解析できるようなものであってもいけません。また、

return "!"+carrier.to_s+":"+Digest::MD5.hexdigest(mobile_id)+"ランダムな秘密の文字列"

といった具合に、hexdigestの外に出してもいけません。そうしてしまうとクライアントに生の"ランダムな秘密の文字列"が渡され、秘密じゃなくなっちゃいますので。

ソースが非公開だったら…

今回の問題はソースコードが公開されたから簡単に再現できたのであって、ソースや使っているアルゴリズムを非公開にしておけば破られないのではないかと思ったとしたら、それは誤解です。例えば、セッションIDに今回のような値やそのバリエーションが使われているかどうかは、次のようにすれば簡単に推測することができます。

  1. 携帯電話を用意して、契約者固有IDを調べる
  2. 以下のコマンドを実行して、契約者固有IDのハッシュ値を求める(コマンドを変えれば、アルゴリズムがSHA-1だろうがXORだろうがBase64だろうが同様にハッシュ値を得ることは可能)
  3. ターゲットサイトに1.で用意した携帯電話を使ってユーザー登録し、セッションIDを抜き出す。
  4. セッションIDに、2.で得られた文字列が含まれるかどうかを調べる

したがって、例えソースやアルゴリズムを非公開にしていても、

誰にでも入手可能で、かつユーザーを識別可能な情報があった場合、それだけから単純に得られる、世に知られているすべてのアルゴリズムのハッシュ値は簡単に推測可能と言える

と思っておいた方がよいでしょう。一方で、独自のハッシュ関数を実装するというのは、理論レベルでの正しい理解が無い限り、きちんとした(この文脈では再現が困難な)ハッシュ関数を作り出すことが難しいという意味で論外です。

全くの余談ですが、本気になった人々が独自のアルゴリズムを使った非公開データ破りにチャレンジするとどういう結果が待っているかというのは、1990年代前後のコンピュータソフトのコピープロテクトとプロテクト解除ソフトのイタチごっこを振り返れば、軍配が上がる先は自明と言えるでしょう。

おわりに

最後に、「Ruby on Rails 携帯サイト開発技法」第8章でのjpmobileの評価についても触れておきます。

第8章のp.256には

(jpmobileの)trans_sidの実装は、やや古い世代の環境を想定して作られたものであるため、現在においては少々陳腐なものとなってしまっています。
(中略)
 そこで次の章では、自前で携帯サイトに必要な機能をRuby on Railsに実装する方法について紹介します。
 Ruby on Railsでの携帯サイト構築についてより深い理解を得られれば、柔軟かつ堅牢な携帯サイトを思いのままに造れるようになるでしょう。

と書かれていますが、jpmobileの変更履歴によれば、

という修正が3年前に加えられています。今でもドコモの機種は全てCookie利用不可という扱いのようなので(docomo.rbのll.78-80)、「iモード2.0」端末の判定ルーチンの追加は必要ですが、高木浩光さんが2010年06月19日の日記(今こそケータイID問題の解決に向けての下記引用部分)で推奨している、Cookieによる実装に近い形に3年前の時点ですでになっていたことになります。

個人的には、このあたりのメソッドを活用してCookie非対応端末向けの処理を組んだ方が、わざわざ自前ですべての機能を再実装するより、楽してかつ安全な実装ができたんじゃないかという気がしますし、trans_sidというアイデアは確かに古い手法かもしれませんが、その実装はむしろ今もって理にかなっており、陳腐と言う表現は当たらないと思います。

話変わって、セッションIDは推測困難なものにしなければならないというのはかなり昔からある話で、2006年11月1日発行の「安全なウェブサイトの作り方」改訂第2版にも記載があります(追記1:コメントで徳丸浩さんに第1版へのリンクと、そちらにも同様の記述があることを教えていただきました。ありがとうございます。)

厳しいことを言うようですが、本書は安易に自前での機能の実装という選択肢を採ると、思わぬところで落とし穴にはまるパターンの好例になっていると思います。

.green. ====

本日のツッコミ(全9件) [ツッコミを入れる]
- 徳丸浩 (2010-06-27 11:06)

興味深い報告をありがとうございます。<br>安全なウェブサイトの作り方第1版は、archive.orgから参照できます。<br>http://web.archive.org/web/20060203051536/www.ipa.go.jp/security/vuln/20060131_websecurity.html<br>該当部分を引用すると、<br><br>これは、セッション管理情報が推測され、第三者に入手されてしまうことを回避する方法です。セッション管理情報の生成規則が単純な場合、その値の推測を容易にしてしまいます。そして、推測したセッション管理情報を使ってウェブページにアクセスすることで、セッション・ハイジャックが行われてしまう可能性があります。セッション管理情報の値にはランダムな数字や文字を与え、攻撃者の推測が困難なものにしてください。<br><br>と、2版とは少し違いますが、「推測困難なもの」という文言はありますね。

- 割となんでも屋 (2010-06-27 11:34)

これって単なるAPIの使用サンプルに難癖つけてみたものの、実はセキュリティに対する注意点が明記されていることに気づいて記事中でお茶を濁しているダメ記事の典型ですね、<br>サンプルはよけいな機能を実装せず話の論点に絞った部分だけにするのは当たり前だと思います。

- 徳丸浩 (2010-06-27 12:15)

割となんでも屋さん。元の本にあるセキュリティ上の注意点は、ログインに関するものであり、一方このエントリの主題はセッション管理の問題についての指摘ですので、「お茶を濁している」ことにはならないと思います。

- hs (2010-06-27 16:16)

徳丸浩さん。<br>第1版へのリンクと引用、ありがとうございました。本文の方に追記させていただきました。<br><br>割となんでも屋さん。<br>解説の冒頭にある特徴の最後の項目とその直後の文章が誤解を与えてしまったようですが、本書のサンプルはユーザー登録してログインした時点で、登録された「お名前」をトップページに表示するものです。これにより携帯端末の契約者固有IDと「お名前」という通常は本名が想定されているであろうデータを結びつけて収集可能となります。<br>つまりこのサンプルは「重要な操作にはパスワードの入力を求めるなどの一定の防護措置を講じ」たとしても、それ以前の状態で利用可能な機能については依然として成りすましての利用が可能である点は押さえておく必要があります。<br>ですので、本の中に明記されているセキュリティに対する注意点と、本エントリで指摘した脆弱性の影響範囲については、きちんと分けて考える必要があると思いますし、通り一遍等に「重要な操作にはパスワードの入力を求めるなどの一定の防護措置を講じる必要はあります」と書いただけでその辺についてちゃんと検討した痕跡が見られない説明やサンプルプログラムだったので、「残念ながら…」という表現を使いました。<br>脚注*1の中の表現として、このエントリが「不要になったと思います」とは書かずに、「違った形になったと思います」という言葉をあえて使ったのも、そのためです。<br><br>また、今回の脆弱性の回避方法については、"セッションIDを推測困難にする"の節で書いたようなプログラムの修正1箇所と、3行ほどの説明の追加で済んだはずですので、<br>> サンプルはよけいな機能を実装せず話の論点に絞った部分だけにするのは当たり前だと思います。<br>というご指摘にも当たらないと思います。

- 通りすがり (2010-06-28 10:29)

お疲れ様です<br>大変参考になる情報をありがとうございます。<br><br>セッションIDを独自のアルゴリズムで生成するなど、推測可能なものは、決して使うべきではないですね。それは広めないといけないと思います。<br><br><br>ですが、今回のこの事例に特化して考えた場合は、そこに結び付かないような気がします。<br><br>セッションIDの管理に不備があるというのは、同じ意見なのですが、(本書が手元になく、憶測の話しで恐縮なのですが)この事例の最大の問題点は、せっかくIPアドレスのチェックを行っているにも関わらず、それをキャリア判定にしか利用していない点ではないでしょうか?<br><br>この事例の場合は、まずは、そこを改善することが、大切だと思いますがいかがでしょうか?

- sanaki (2010-06-28 12:30)

*5 は、そんなに単純ではないかも。<br>http://memorva.jp/memo/mobile/http_user_agent.php<br>http://www.nttdocomo.co.jp/service/imode/make/content/spec/useragent/<br>DoCoMo/2.0 の次の機種名まで見ないとダメかも。<br>iMode2.0 は大規模な仕様変更なのだから、もっと上位の User-Agentを変更すべきだったのでは、と思う今日この頃。

- hs (2010-06-28 18:49)

通りすがりさん。<br>IPアドレスのチェックについては、携帯アプリでは使えない事情があります。<br>まず、WASForum 2010で徳丸さんが発表されてたんですが、同じ端末からのアクセスでも、ゲートウェイのIPアドレスがころころ変わるそうです。このため、セッションIDとIPアドレスを1対1で結びつけてチェックするということはできません。<br>かといって、キャリアが公開しているIPアドレス範囲のような広い括りでチェックすると、脚注*2に書いたような条件が満たされたり、スマートフォンやPCからキャリアのゲートウェイ経由でアクセスされたりすると、やはり対処できません。<br><br>というわけで、IPアドレスのチェックでは有効な解決にならない、というのが私の見解です。<br><br>sanakiさん。<br>情報ありがとうございます。脚注に追記させていただきました。<br><br>User-Agentを変更すべきだったというのは、まったくもってその通りだと思います。キャッシュの搭載量で判断なんてかなり嫌ですし。<br>http://www.nttdocomo.co.jp/service/imode/make/content/browser/html/useragent/

- 通りすがり (2010-06-28 20:56)

なるほどですね。<br><br>キャリアのGWかどうかを確実に判別するのは難しいとの前提条件があると理解しました。<br><br>となると、キャリアのGWなどの話題にはふれないほうがいいと感じます。(特に下のくだり付近など)<br><br>「キャリアのゲートウェイを通過してきてなおかつ契約者固有IDを偽装できなければ、セッションハイジャックは成立しません。」<br><br><br>ちなみに、スマートフォンとフィーチャーフォンが利用するIPアドレス帯域は私の知るキャリアでは重複しません。<br>また、PCからキャリアGWを利用できることは一部のキャリアの特殊な使い方以外無理だと私は思っています。<br>(1対1で紐付ける必要はないかと)<br><br>との前提条件が私の頭の中にはありました。<br><br><br>あくまで書籍の事例においてとの記事だと理解しているつもりではありますが、結局この手の記事は突き詰めると、固有IDの問題を話さざるを得ない気がします。<br>hsさんがおっしゃるとおり、プロテクトがいずれ破られるのは、示してくださった解決手法においても同じ事のような気がします。<br>もちろん、容易では無くなると言う観点からは実施すべきだと思います。<br><br>なかなかこういう記事は難しいですね。<br>レスありがとうございました。

- hs (2010-06-29 20:10)

通りすがりさん。<br><br>本のほうをお持ちで無いと言うことで分かりにくかったかもしれませんが、引用されている箇所は<br>サンプルプログラムの説明と事実関係について記述しており、そこを出発点にどうやればセッション<br>ハイジャックできるのか、という考察へと進んでいく文脈です。したがって、ここの説明を省略すると<br>逆に本を読んでいる人からは、なされている対策に触れずに文章を組み立てているような印象を与えかねず、<br>それはかえってまずいと思います。本が無くても、本文中のリンクからサンプルプログラムをダウンロード<br>できるようですので、興味があればダウンロードして読んでみてください。<br><br>あと、<br>> また、PCからキャリアGWを利用できることは一部のキャリアの特殊な使い方以外無理だと私は思っています。<br>とのことですが、攻撃の成立にはそれだけで十分です。<br>なぜなら、今回のサンプルは、悪意のある人間の方がその方法を利用すれば、アプリで対応している<br>全てのキャリアの任意のユーザーに成りすまし可能だからです。<br><br>最後に、このエントリの記述が、「契約者固有ID」を「アクセス元のIPアドレス」と読み替えても成立する、<br>ということを念頭においてもう一度読み直していただくと、今の結論とは違った側面が見えてくるかもしれません。