気を付けたい貧弱なセッション管理Webアプリケーションに潜むセキュリティホール(3)

» 2003年07月09日 00時00分 公開
[国分裕三井物産セキュアディレクション]

※ご注意

他社および他組織のWebサイトなどへのポートスキャンおよびデータの取得などの行為で得た情報を侵入などに悪用するか、または同じ目的を持つ第三者に提供した時点で違法となります。ご注意ください。

本稿の内容を検証する場合は、必ず影響を及ぼさない限られた環境下で行って下さい。

また、本稿を利用した行為による問題に関しましては、筆者および株式会社アットマーク・アイティは一切責任を負いかねます。ご了承ください。



 「第2回 顧客データがすべて盗まれる」は、クロスサイトスクリプティング(XSS)と同様に実際のプログラミングを行うプログラマの責任であるという対策で、最も危険と思われるSQL InjectionとOS Command Injectionについて紹介した。今回は、プログラミング以前の設計段階で潜り込むセキュリティホール――見落としがちなセッション管理の脆弱性について説明していく。

Webサイトにおけるセッション管理とは

 まず、なぜセッションというものが必要なのかを理解するために、HTTP(Hypertext Transfer Protocol)について知っておく必要がある。

 HTTPは、リクエストとレスポンスが1対になっているプロトコルである。

図1 リクエストとレスポンスが1対になっているプロトコルであるHTTP 図1 リクエストとレスポンスが1対になっているプロトコルであるHTTP

 図1で、(1)「GET /index.html HTTP/1.0」というリクエストに対して、サーバが (2)「/index.html」の内容を含んだレスポンスを返す。(3)(4)(5)(6)も同様の動作となる。ここで、HTTPの特徴の1つであるステートレス性という点に注目する。クライアントBがリクエスト(3)と全く同じ内容のリクエスト(5)を送信すると、レスポンス(4)と同じ内容のレスポンス(6)が返ってくる。クライアントAとクライアントBが別人であることや、クライアントBが過去に/index.htmlにアクセスしていないことには一切影響されない。

 会社案内や製品紹介など、だれがいつ見ても同じページを表示したい場合にはこれで十分だ。しかし、(1)(3)を関連付けて同じクライアントからの一連の操作(セッション)であることを識別したい場合に、HTTPのステートレス性が壁となる。

図2 クライアントからサーバへのリクエスト 図2 クライアントからサーバへのリクエスト

 図2で、リクエスト

(1)にはAさんを特定するIDとパスワードが含まれているので、対するレスポンス(2)はAさん専用のページを返すことができる。しかしリクエスト(3)にはAさんを特定する情報が含まれていないので、だれからのリクエストなのかを識別することができなくなってしまう。

 そこでセッション管理が必要となる。HTTP自身はステートレスなためアプリケーションでの仕掛け・工夫が必要となる。

図3 ステートレスであるHTTPためセッションID(整理番号)が必要となる 図3 ステートレスであるHTTPためセッションID(整理番号)が必要となる

 リクエスト(1)に対するレスポンス(2)に、何らかの整理番号を付けて返すようにする。次にクライアントがリクエスト(3)を出すときに一緒にその整理番号を付けて送信する。これによってサーバはリクエスト(1)を送信してきたのと同じクライアントであると認識できるようになる。以下この整理番号のことを「セッションID」と呼ぶことにする。

 これで「一連の操作を行う個人を特定できるので、オンラインショッピングや個人情報のやりとりを行っても大丈夫だ」と考えるのは早すぎる。セッションIDの発行方法や管理方法が貧弱だと致命的なセキュリティホールになってしまう。

セッションハイジャック

 前述のとおり、HTTP はステートレスなプロトコルである。いくらセッションの概念を導入したところでこれは変わらない。

図4 セッションハイジャックの動作例 図4 セッションハイジャックの動作例

 クライアントAがリクエスト(1)を送信し、セッションIDが発行される。クライアントAがこのセッションIDを使ったリクエスト(3)を送信するとレスポンス(4)が返ってくる。ところが攻撃者が、同じセッションIDを使ってリクエスト(3)と同じリクエスト(リクエスト(5))を送信すると、レスポンス(4)と同じ内容のレスポンス(6)が攻撃者に返ってくる。つまり本来であればクライアントAしか見られないはずのページを攻撃者も見ることができてしまう。さらにAになりすまして以降の処理を継続することもできるだろう。これがセッションハイジャックだ。セッションリプレイと呼ばれるものもあるが攻撃の仕組みは同様だ。

 攻撃者がセッションハイジャックを行ううえでポイントとなるのが、いかにして他人に発行されたセッションIDを取得するかだ。いくつか方法がある。

セッションID推測する

 もちろん攻撃者の勘ではなく、何らかの根拠に基づいてセッションIDを推測することになる。あるサイトに何回かアクセスしたときに、次のようなセッションIDが発行されている場合はどうだろうか。

105963
105965
105966
105968
105969
105971
リスト1 発行されているセッションID例

 これはだれでも分かるだろう。このサイトではセッションIDに連番を使っているのではないか、という推測ができる。いくら何でもこれはないだろうと思われるかもしれないが、過去に私が診断したサイトでも数件このパターンがあった。

 どうにかして推測しにくくしてしまおうと考えているサイトもある。次の場合はどうだろう。

MTM2ODcyNjIxMzY5Cg
MzI0ODYwMTAxMjI1Cg
NDQ4ODU0NDQxMTU2Cg
NzU2ODQ0MzIxMDI0Cg
OTQwODM5ODYwOTYxCg
MzIzODk1NjA4NDEK
:
リスト2 推測されにくいセッションID例

 何となく似ているものの、一見ではどのように生成しているのか判別するのは難しいだろう。これは上記リスト1の下3けたを取り出し(963)、逆順にしたものを先頭に付け(369963)、それを2乗し(136872621369)、BAse64エンコードし(MTM2ODcyNjIxMzY5Cg==)、最後の「=」を削ったものだ。攻撃者はこの生成方法を見破るかもしれないし見破れないかもしれない。攻撃者との知恵比べゲームを楽しみたいのならそれもいいが、業務で使うアプリケーションでゲームをするのはやめておいた方がいいだろう。

 簡単に予測しにくいセッションIDを作るには、乱数とハッシュを使う方法がある。リスト3はその両方を使ってみたPerlプログラムの例だ。

#!/usr/bin/perl
require 5.004; 
use Digest::MD5 'md5_hex';
print md5_hex rAnd;
リスト3 予測しにくいセッションIDを作るためのPerlプログラム例

 出力はリスト4のようになる。

1f10ba97635b888fe4f6a43753ff8a5b
85226f534db05f6ac5a0a72aaafe94bc
384a8e93a876caae6e3e4bdbc1163992
0d1c51b8cbf13a44ab7881c819b2cda2
2e5544ac6d6508cf51f4446036ce9df2
リスト4 Perlプログラムによる予測しにくいセッションID例

 乱数だけでも十分推測しにくくなるが、ハッシュを併用することで乱数を使っていることさえも推測しにくくなる。ハッシュの元になる値は乱数だけでなくてもよい。ハッシュ化するとハッシュ元の値を導き出すことは事実上不可能になるので、ユーザー特有の情報を含ませてしまっても安心だ。攻撃者からすると、万が一乱数を推測できたとしても「kokubu-0.0534996088399602」なのか「0.0534996088399602-kokubu」なのかも推測しなければならなくなり、さらに推測がしにくくなるだろう。ただし、ユーザー特有の情報だけからハッシュを計算するのはお勧めできない。その情報が変わらなければ何度ハッシュを計算しても同じハッシュ値しか生成されないからだ。

必ず乱数を併用しよう。

総当たりで有効なセッションIDを見つけ出す

 推測しにくい方法でセッションID を生成していたとしても、片っ端から調べることで有効なセッションIDを見つけ出すという方法がある。仮にリスト1で連番を使っていると分からなかったとしても、数字6けたなのは分かる。それならば 000000〜999999 すべてを試してみればそのうちヒットするだろう、という攻撃だ。ブルートフォース攻撃とも呼ばれる。

 ちなみに手元の LAN 環境で試してみたところ、数字6けたの場合には2時間弱ですべてのIDをチェックすることができた。この時間はすべてのIDをチェックする時間なので、範囲内に有効なセッションIDがあれば、総時間÷有効なセッションID数でいずれセッションIDを見つけ出すことができる計算になる。

 これに対する対策は簡単で、使用する文字種を増やすこと・文字列長を長くすることだ。文字種を増やし長くすることで、調べなくてはいけないパターンを増やすことができる。数字6けたの場合は10^6 = 100万通りだが、英数字8けただと36^8 = 約2兆8000億通りとなる。安全な文字種数、長さサーバのスペック、ネットワーク環境、HTTPDの設定などによって異なってくるため、推奨値を挙げることはできない。参考までにいくつかのアプリケーションサーバが使っているセッションIDの文字種と長さを挙げておく。

PHP:128bit(PHPSESSID=f08b925af0ecb52bdd2de97d95cdbe6b)

ASP:32bitのIDを暗号化(ASPSESSIONID=PUYQGHUMEAAJPUYL)

JSP:大小英文字+数字の組み合わせによる52文字


 セッションID はパスワードと違って、ユーザーが記憶したり入力したりする必要はないものだろうから、余裕をもって十分長くしておきたい。

 セッションの有効期間を短くするのも効果がある。総当たりでの検索に1時間かかるなら有効期間を1時間未満にしてしまい、万が一正しいセッションIDが見つかってしまったとしても見つかったときには無効になっているようにするのだ。短ければ短いほどいいのだが、あまり短くしすぎると正規のユーザーがアクセスした際にいつの間にかセッションが無効になっていたということになりかねない。正規のユーザーがどのくらいの頻度でアクセスしてくるのかを把握したうえで有効期間を決めてほしい。

セッションIDを盗む方法の1つ“盗聴”

 いくら推測しにくくて総当たりでも発見できないセッションIDを使ったとしても、有効なセッションIDを盗まれてしまうとひとたまりもないだろう。セッションIDを盗む方法の1つにネットワークの盗聴が挙げられる。クライアント/サーバ間の経路に

Snifferを仕掛けておき、流れるデータからセッションIDを抜き出す。これはHTTPSによる暗号化通信を行えば回避できるだろう。

 XSSの脆弱性を使ってセッションIDを盗むこともできる。クロスサイトスクリプティング対策の基本(中編)で、「単にcookieが盗まれただけではほとんど被害は起きない。被害が起きるのは、このcookieの中に含まれる情報の内容に問題があったり、cookieの使い方に問題があるWebアプリケーションを使用している場合だ」としたが、その1つがcookieを格納したセッションIDでユーザー認証をしている場合だ。ちなみに、cookieだけでなくURLやフォームに含まれているセッションIDを盗める場合もあるだろう。

 盗まれてしまったセッションIDが使われた場合に、それが盗まれたものかどうかを判別するのは非常に難しく、完全に排除するのは不可能だろう。盗まれないようにする対策をしっかりやっておこう。

Session Fixation攻撃

 アプリケーションが生成したセッションIDを盗むことができないなら攻撃者自身が生成したセッションIDをクライアントに使わせてしまおう、という攻撃だ。

図5 攻撃者自身が生成したセッションIDを使わせるSession Fixation攻撃 図5 攻撃者自身が生成したセッションIDを使わせるSession Fixation攻撃

 クライアントがID、パスワードなどにより認証をする際に、セッションID(例えば12345)を付加してリクエストを送信する。すると認証後にアプリケーションが発行するセッションIDが12345になってしまうのだ。このセッションIDがクライアントA自身が決めたセッションIDならば、自分で自分の首を締めるだけなので大した問題ではない。ところがXSSなどを併用すると、攻撃者が決めたセッションIDをクライアントAに使わせることができるのだ。そうなると攻撃者はセッションIDを推測する手間もなく、苦労せずにクライアントAのセッションIDを手に入れることができるだろう。

 Session Fixationが公表されたとき、まさかこんな動作をするアプリケーションはないだろうと思っていたのだが、その後診断を行ったいくつかのサイトでこの問題が見つかった。このような動作をする理由がいまいち把握しきれていないのだが、これは明らかにアプリケーションの問題だろう。

 セッションIDはアプリケーションで生成してそれを使うようにしよう。

セッションIDはパスワードの代わり

 セッション管理方法でミスがあると、システム全体の認証強度を下げることにもなりかねない。

図6 システム全体の認証強度を下げかねないセッション管理 図6 システム全体の認証強度を下げかねないセッション管理

 図6の(1)(2)では、IDとパスワードによる認証を行っており、一定の強度を保っているだろう。次の(3)(4)ではセッションIDが有効かどうかだけで認証を行っていることになる。このセッションIDの強度が低ければ、いくらID、パスワードによる認証が強固であったとしても全く無駄になる。これらの認証強度のうちの低い方がこのシステムの認証強度となる。複雑なパスワードを見つけ出すよりもリスト1のような連番を推測して認証を回避する方が楽、ということが直感的にお分かりいただけるだろう。

 セッションIDについても、IDやパスワードなどの認証情報と同等以上の扱いをするようにしておこう。

セッションID以外のセッション管理

 一連の操作の流れを追うのではなく、単にユーザーを特定したいだけであればほかの方法もある。1つがBasic認証だ。

図7 Basic認証を用いている場合の認証表示例 図7 Basic認証を用いている場合の認証表示例

 Webサイトを見ていて、図7のようなユーザー名とパスワードを入力するダイアログが表示されたことがあるだろう。このようなサイトはBasic認証を使っている。ユーザー名とパスワードを入力してOKボタンを押すと、ブラウザは次のようなヘッダの中にエンコードしてサーバに送信する。

Authorization: Basic a29rdWJ1Om1pY2hhIGl5YQ==


 サーバはこれをデコードし認証を行う。Basic認証は、ほとんどのWebサーバが対応しているため手軽に利用できるのだが、盗聴に弱いという欠点がある。上記Authorizationヘッダの値は、ユーザー名とパスワードをつなげてエンコードしただけであるので、だれでもデコードしてユーザー名とパスワードを読み取ることができてしまう。Basic認証はもう1つ、ログオフができないという問題がある。ブラウザが起動されている間は常にこのヘッダが送信され続けている。止めるためにはブラウザを終了するしかない。Basic認証の認証情報はXSSでは漏えいしないというメリットがあった。しかしXSSでBasic認証の認証情報も漏れることがあるということが分かったため、手軽さ以外ではお勧めできない方法になってしまった。

 代わりに、Digest認証を使う方法がある。ここでDigest認証について詳細な説明をすることは避けるが、盗聴やXSSによってAuthorizationヘッダが漏えいしても、ユーザー名とパスワードを導き出すことができないようになっている。Basic認証よりも優れているのだが、比較的新しいブラウザしかDigest認証に対応していないため、古いブラウザを使っているユーザーのアクセスを想定する場合には採用できないかもしれない。

 もう1つ、SSLのクライアントの証明書を使う方法がある。これは現在あるWebでの認証方法の中では最も安全な方法だと思われる。個人を特定することができ、盗聴・なりすましからも守られる。ただし1証明書当たり3000円程度の費用が掛かるので、大規模なサイトでの導入は難しいだろう。

 それぞれ、対象とするユーザーや規模によって使用を検討してほしい。


 前回説明したOSコマンドインジェクションやSQLインジェクションでは1回のアクセスで大量のデータを盗むことができるのに対し、セッションハイジャックでは1回のアクセスで1人分のデータしか盗むことができない。しかしさまざまなセッションIDを使用して何度もアクセスすることで結果的には大量のデータを盗むことができるため、甘く見ていると大変な被害に遭ってしまう。

 何度もアクセスしてくるためログを見ていれば発見できるかもしれないが、1日中じっとログを眺めていたくないのならばセッション管理方法に問題がないかを再確認することをお勧めする。

第2回」へ

第4回」へ


著者紹介

国分裕(こくぶ ゆたか)

三井物産セキュアディレクション勤務。セキュリティコンサルタントとして、不正アクセス監視やセキュリティ検査などに従事している。金融機関、官公庁、大手製造業などへのセキュリティシ ステムの導入、セキュリティ 検査などの実績を持つ。

三井物産セキュアディレクション

主に、不正アクセス監視サービス、セキュリティ検査、セキュリティポリシー策定支援などのサービス提供している。また、セキュリティに関する教育サービスも実施中。


Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。