追記:より新しい情報については間違いだらけのHTTPセッション管理とその対策をどうぞ。
PHPには広く知られているにも関わらず放置されている既知のセキュリティ脆弱性が幾つかあります。その一つがセッションモジュールのセッションアダプション(Session Adoption)脆弱性です。この脆弱性は現在広く利用されているWebアプリケーションの安全性に、非常に大きな影響を与える脆弱性です。
セッションアダプション脆弱性とはセッション固定化攻撃を可能とする脆弱性の一種です。セッションアダプションに脆弱なセッション管理システムは、ユーザ(ブラウザ)が送信してきた未初期化のセッションIDを受け入れ、セッションを初期化してしまいます。PHPに限らず、RailsやJavaのフレームワーク等、多くのWebフレームワークに発見されている脆弱性です。
セッションアダプション脆弱性を利用すると、攻撃者は長期間に渡って他人のIDを使う成りすましが可能になる場合があります。IDを盗みたい犯罪者には利用価値の高い脆弱性です。
この脆弱性は、10年以上前から問題視されている国別TLD(ccTLD)の属性ドメイン(国別Top Level Domain, co.jp, or.jpなどのドメイン)にも下位ドメインからクッキーが設定でき、サブドメインのクッキー設定が無視される(送信順序、優先順位がいい加減)、というとんでも無いクッキーの仕様と組み合わせると、大量の他人のユーザセッションIDを取得(設定)し、成りすます事ができます。
セッションアダプション問題は全てのPHP、PHP 4.4.9/PHP 5.2.8/PHP 5.3/PHP 6.0に共通する脆弱性です。
最近、影響範囲はかなり狭くなりましたが、まだまだ注意が必要な脆弱性であり、根本的な解決にはパッチと適切なセキュリティ対策が必要です。
国別TLDの属性ドメインにクッキーが設定できるバグはFirefox2を最後に広く使われているブラウザにはこの「仕様」は無くなりました。Firefox3のリリースにより、国別TLDの属性ドメイン問題はやっと、ほぼ解決した、と言える状態になりました。
まだFirefox2を利用されている方は早急にFirefox3にバージョンアップした方が良いでしょう。このブログをご覧の方でも1割ほどが、まだFirefox2です。
国別TLDの属性ドメインにクッキーが設定できる問題は、少なくともFirefox2で修正されるべきでしたが、何故修正されなかったのか詳しい理由は知りません。この問題は.com, .netなどのgTLDの独自ドメインで運用しているサイトはあまり影響を受けないので、脆弱性の危険性が誤解され、放置されていたのかも知れません。
国別TLDの属性ドメインにクッキーが設定できるのも問題ですが、詳しくは後ほど説明しますが、古いブラウザは親ドメインのクッキーが優先されていましたが、最近のブラウザは子ドメインのクッキーが優先されるようになっています。この為、以前に比べて脆弱性の影響は随分減っています。
ブラウザ開発側の対応も褒められたものではありませんが、PHP開発側の対応はさらに酷いです。数年前、PHP 5.1.xの頃に、いい加減この脆弱性を修正しよう、とStefan Esser氏によりパッチ付きで修正案が提案されたのですが未だに修正されていません。私自身もPHP 4.4.9リリース前にPHP 4.4, PHP 5.2両方にセッションアダプション脆弱性を修正するよう、PHPセキュリティレスポンスチームに提案したのですが、予想通り修正されませんでした。脆弱性の危険性は説明したのですが、危険性を理解していないとしか思えません。
直らないものは仕方が無いので各自で対処するしかありません。各自で対策できるよう、個人Wikiではパッチも公開しています。
対処方法の前に、なぜこの脆弱性を最悪と考えているのか、理解していただく為に攻撃方法を紹介します。
攻撃方法1 – サブドメイン
レンタルサーバなどで独自にドメインを取得していない場合、サブドメインでサイトを区別している場合がよくあります。例えば、example.jpがレンタルサーバドメインでuserAやuserBのサイトは
userA.example.jp
userB.example.jp
となっているサービスが良くあります。クッキーの仕様的にはuserA.example.jp, userB.example.jp のどちらもexample.jp ドメインに対してクッキーを設定できます。しかも、example.jp にクッキーが設定されている場合、サブドメインのクッキーより優先されてしまう場合があります。レンタルサーバのFAQなどには「example.jp にクッキーを設定しないで下さい。全てのユーザに見えてしまいます」と記載されているところもあるようです。
つまり、PHPのセッションモジュールのデフォルトセッション名のPHPSESSIDに適当な値を設定するとサブドメイン共通のセッションIDが作れてしまいます。セッション名を変えて機械的な攻撃を防いだとしても、サイトを覗けばセッション名が直ぐに分かってしまいます。
ログイン時にsession_regenerate_id()を呼んでいても、セッションアダプションに脆弱なセッションモジュールでは意味がありません。親ドメインのクッキーが優先され、セッションIDは変わらない事があるからです。
例えば、悪意があるサイト、evil.example.jp は、被害者のユーザがevil.example.jp を訪問すると、そのユーザ「固有」のPHPSESSIDを有効期限2038年で設定します。攻撃者は既に発行済みのセッションIDで他人になりすまして、userA.example.jp, userB.example.jp 等、ログインできるサイトが無いか試すだけです。
userA.example.jp, userB.example.jp の様にユーザ毎にサブドメインを持つ場合、仮に全てのuserA, userB が信用できても各ユーザが利用しているアプリケーションにXSSが一つ有るだけで、example.jp 全体が危険にさらされる可能性があります。JavaScriptでクッキーの操作が可能だからです。
この例では、example.jpを利用していますがFirefox2に対してはexampleの部分がco,ac,orでも同じ攻撃ができました。つまり
example.co.jp, example.ac.jp, example.or.jp
などに共通のセッションIDを設定することが可能だったのです。この問題の影響範囲の大きさは説明する必要も無いと思います。
攻撃方法2 – 狙い撃ち攻撃
セッションアダプションに脆弱なPHPとその攻撃に対して脆弱なアプリケーションを利用すると、同僚や同級生など身近な人のセッションを乗っ取りは簡単です。
クッキーは通常テキストファイルに保存されています。知人がwww.example.com のサービスを利用しているとします。同僚・友人のPCに保存されているクッキーファイルを編集してexample.com に2038年まで有効なセッションIDを書くだけです。
クッキーファイルは編集可能なので、隙を見て編集するだけです。有効期限を比較的短めに設定すればキーロガーのように証拠も残りませんし、ネットワークに不審なパケットも流れません。
セッションアダプションに脆弱なPHPを利用しているなら、session_regenerate_id()を呼び出しているPHPアプリでも攻撃できます。クッキーを保存しているファイルを読み込み専用にするだけで、セッションIDクッキーの再設定を防き、セッションIDの固定化が可能です。
サーバ側の防御方法
セッションアダプション脆弱性を無くすのが第一です。何年も前から筆者のWikiにて、Stefan Esser氏が作成したパッチを多少修正して公開しています。
http://wiki.ohgaki.net/index.php?PHP%2Fpatch%2FStrictSession
このパッチだけでは実際に攻撃された時、ログインできなくなる、などの問題が発生します。次の「アプリの防御方法」の対策も必要です。
パッチを適用するだけで、攻撃パスを無効化できます。成りすましは防げるようになりますが、DoSの可能性が新たに生まれます。詳しくは次の「アプリの防御方法」を参照してくだい。
アプリの防御方法
セッションアダプションに脆弱なセッションモジュールでは完全な対策は取れません。しかし、セッションアダプションに脆弱なセッションモジュールを使っていても防御効果は期待できます。
www.example.co.jp にセッションIDを設定する場合、www.example.co.jpにセッションIDを設定する前にexample.co.jp, co.jp, jp(TLDですが念のため)に設定されているかも知れないセッションID用のクッキーを削除します。(過去の有効期限の空クッキーを送る)ログイン時には必ず上位ドメインのセッション管理用のクッキーをクリアした上で、新しいセッションIDを発行するようにします。これで多くの攻撃を防げます。これでかなり安全になるのですが、このようなクッキー削除を行っているアプリケーションはほとんどありません。
上位ドメインのクッキー削除を怠ると、パッチ済みのPHPではセッションIDの更新ができなくなります。初期化済みのIDを用意しても、ブラウザが繰り返し未初期化のIDを送信するからです。上位ドメインのクッキーを削除しないとDoS状態になってしまう事があります。パッチの有無に関わらず、上位ドメインのクッキーを削除すべきです。
セッションID管理用のクッキー名を変更するのも気休めにはなります。セッション管理用のクッキー名は公開情報なので、この対策は本気の攻撃者には無意味ですが、特定の攻撃目標を設定していない攻撃者には有効な防御策です。
アプリケーションがどれくらいPHPのセッションアダプション脆弱性に対して弱いのか調べるのは簡単です。ログイン時に実行すべきセッションIDを再生成するsession_regenerate_id()、上位ドメインのセッションIDクッキーを削除するsetcookie()が実行されているかどうか調べるだけです。強固なセキュリティが要求されるWebmailシステムですが、PHPベースのWebmailシステムで最も広く利用されていると思われる2つのシステム、SquirrelMailとRoundCubeをチェックしてみました。2つともsession_regenerate_id()さえ呼び出していません。
Webmailシステムの場合、http://webmail.example.com/ や http://www.example.com/webmail など形式のURLを持っています。Webmail自体、XSS脆弱性が度々発見されています。仮にWebmailにXSS脆弱性がなくても、サイトのどこかにXSS脆弱性が1つ有るだけで、セッションアダプション脆弱性を悪用するために有効期限の長いセッションIDを設定され、長期間ユーザに知られる事無くセッションが盗まれる(成りすまされる)リスクが非常に高いです。
通常、XSS脆弱性だけではログインしている場合のセッションIDしか盗めません。しかし、セッションアダプションに脆弱なセッション管理の場合、永続的にセッションを盗む事を可能にします。
ユーザの防御方法
定期的にクッキーを全部削除したり、保存されているクッキーを確認する方法が有効ですがあまり現実的ではありません。
クッキーがどのように処理されるかはブラウザとフレームワークによって異なります。参考までにどのブラウザが比較的安全か調べてみました。
cookie.php
<?php
if (!empty($_GET['set'])) {
setcookie('var','jp',time()+400,'/','jp');
setcookie('var','co.jp',time()+400,'/','co.jp');
setcookie('var','example.co.jp',time()+400,'/','example.co.jp');
setcookie('var','www.example.co.jp',time()+400,'/','www.example.co.jp');
}
echo "<pre>";
var_dump($_COOKIE);
hostsファイルで、jp, co.jp, example.co.jp, www.example.co.jp をホスト名としてアクセス出きるように設定し、ブラウザからhttp://www.example.co.jp/cookie.php にアクセスして動作を調べてみました。
詳しく各ブラウザの動作を調べていないので、もし古いOperaの様にIPアドレスが引けないドメインに対してクッキーを設定しない動作をしているのであれば、実際の動作と異なる事になります。(co.jpなどは普通IPアドレスが設定されていない)しかし、スピア型の攻撃は可能なので参考にはなると思います。
MomongaLinux5 64bit
Firefox 3.0.5 | www.example.co.jp のクッキーが優先される |
Konqueror 4.1.0 | www.example.co.jp のクッキーが優先される |
WindowsXP 32bit
Firefox 2.0.20 | co.jp のクッキーが優先される |
Internet Explorer 6.0 SP2 | example.co.jp のクッキーが優先される |
Internet Explorer 7.0 | クッキーが設定されない。example.co.jp には設定される。 |
Internet Explorer 8.0 beta1 | example.co.jp のクッキーが優先される |
Safari 3.2.1 | co.jp のクッキーが優先される。 |
Opeara 9.5.0 | www.example.co.jp のクッキーが優先される。 |
Google Chrome 1.0.154.43 | example.co.jp のクッキーが優先される |
このテスト結果からは、SafariとFirefox2の動作が極めて悪い動作であることが分かります。
これらの動作は、クッキーの設定順序なども動作に影響しているかも知れません。次の「クッキーの実装がいい加減な理由」を見て頂ければ分かりますが、どのように動作するのかはブラウザの実装が関係しています。
実際のWebアプリケーションがどのように振る舞うかは、Webアプリケーションが利用しているフレームワークの実装に影響されます。PHPの場合はPHP本体がクッキーのデコードを行い配列($_COOKIE)に保存するので、フレームワークによって動作が変わることはありません。他の言語の場合、クッキーのデコードはフレームワークが行うので同じ言語でもフレームワークが異なると動作が異なるでしょう。
PHPで作られたアプリケーションの場合、Firefox3, Konqueror, Operaを利用している方がより安全であることが分かります。
クッキーの実装がいい加減な理由
現在のクッキーの実装はNetscapeのクッキー仕様となっています。手抜きで調べていないので、間違っていたら指摘していただきたいのですが、たしかNetscapeの仕様では複数のクッキーが設定されている場合、どういった順序で送信するのか決まっていません。パス、ドメインなどで複数のクッキーを設定することは簡単です。
以下はFirefox3でcookie.phpにアクセスした時のHTTPリクエストヘッダです。
GET /cookie.php HTTP/1.1
Host: www.example.co.jp
User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; ja; rv:1.9.0.5) Gecko/2009011916 Momonga/3.0.5-1m.mo5 Minefield/3.0.5 FirePHP/0.2.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: var=www.example.co.jp; var=example.co.jp
複数クッキーが設定されている場合、どのような条件で設定されたか判別する情報なしに列挙されている事がわかります。
他のブラウザでは試していませんががFirefox3ではcookie.phpのクッキー設定順序を反対にして試してみました。クッキーの設定順序に関係なく、この順番に送信されるようです。Firefox3の場合、優先順位が高いと思われるクッキーが先に書かれていると思われます。
PHPでは最初に現れたクッキーが優先されるので、PHP+Firefox3の組み合わせは比較的安全に利用できることが分かります。
繰り返しになりますが、Firefox3であれば全てのフレームワークが比較的安全に利用できるとは仮定できません。フレームワークのクッキー初期化の仕様によります。他の言語のフレームワークでの動作も調べたかったのですが、手抜きで調べていません。調べた方がいらっしゃる場合は是非教えてください。
まとめ
個人的にはこの脆弱性は既知のPHPの脆弱性の中では、影響範囲、攻撃の難易度、攻撃された場合の検出可能性などを総合的に評価して、最悪の脆弱性だと思っています。パッチは何年も前から公開されています。しかし、セッションアダプションを修正したパッチを適用したPHPビルドを採用しているディストリビューションは見たことがありません。(過去のMomongaLinuxを除く。今のMomongaLinuxにはパッチは当ててありません…)
PHPセキュリティレスポンスチームに連絡しても、残念ながら無駄な努力に終わるので、利用されているディストリビューションに問題を報告して、パッチを適用するようにお願いするのが脆弱性対策のもっとも近道だと思います。
パッチを適用していても、アプリケーション側で上位ドメインのクッキーを削除するコードを追加する対策も必ず行う必要があります。これを怠ると、上位ドメインにクッキーを設定するだけでDoSが可能になります。
この脆弱性を利用すると、セッションアダプションに脆弱なサイトに対して非常に簡単に成りすましが可能になります。出来心で他人に成りすまし、人生を棒に振らないようにしてください。
参考
http://wiki.ohgaki.net/index.php?PHP%2Fpatch%2FStrictSession
http://wiki.ohgaki.net/index.php?PHP%2F%E8%84%86%E5%BC%B1%E6%80%A7%E3%83%AA%E3%82%B9%E3%83%88%2FPHP4
http://wiki.ohgaki.net/index.php?PHP%2F%E8%84%86%E5%BC%B1%E6%80%A7%E3%83%AA%E3%82%B9%E3%83%88%2FPHP5
http://www.futomi.com/lecture/cookie/specification.html
http://www.faqs.org/rfcs/rfc2109.html
http://www.faqs.org/rfcs/rfc2965.html