他社および他組織のWebサイトなどへのポートスキャンおよびデータの取得などの行為で得た情報を侵入などに悪用するか、または同じ目的を持つ第三者に提供した時点で違法となります。ご注意ください。
本稿の内容を検証する場合は、必ず影響を及ぼさない限られた環境下で行って下さい。
また、本稿を利用した行為による問題に関しましては、筆者および株式会社アットマーク・アイティは一切責任を負いかねます。ご了承ください。
前回はWebアプリケーションのセッションまわりの検査について説明した。今回は、もう1歩踏み込んで、セッション管理の検査方法を手順を追って説明しよう。
検査について説明していくには、検査対象のWebサイトが必要である。しかし、当然のことながら実際のWebサイトを用いるわけにはいかないので、筆者の手元にあるデモサイトを用いて説明していくことにする。
前回説明したセッションまわりの話は「全体のセッション管理」*1のことである。アプリケーションの認証状態のステータスとしては、ログインしていない状態である「未認証状態」、ログイン後の状態である「認証済み状態」の2種類がある。
単純なアプリケーションでは、この2種類しかないのが普通だが、大きなアプリケーションになると、もう少し複雑になる。ここで出てくるのが「局所的なセッション管理」である。局所的なセッション管理とは、アプリケーションの一部分におけるセッション管理である。今回は、このようなセッション管理を行っている場合の検査方法について説明する。
*1
「全体のセッション管理」および「局所的なセッション管理」という表現は筆者が独自に定義したものである。
今回も、前回の説明で使用したWebアプリケーションを使用して説明を行う。前回示した画面遷移図を見ていただきたい(図1)。
このサイトで提供されている機能でこのような局所的なセッション管理が行われているのは、登録情報の参照/変更を行う個所である。
まずはアクセスしてみよう。アクセスを行う前に検査用ツールAchilles(http://packetstormsecurity.nl/web/achilles-0-27.zip)をセットしておくのを忘れずに。
TOPページにアクセスし、登録情報の参照/変更の画面までたどる。
このときのクライアントのステータスは、「全体のセッション管理用Cookieを持ち、認証済みの状態」である。また、Cookie内のセッションIDは、「年月日+時刻+通し番号」であり、セッションIDに対応するユーザー情報はサーバ側で管理されている。ここまでは前回説明したとおりだ。
この状態で、登録情報の参照/変更画面にアクセスを行ってみる。登録情報の参照/変更画面へはリンクによるアクセスとなっている(http://www.example.com/userinfo.cgi)。
これをクリックすると、再認証画面が現れる(図2)。
FORMの中身を見てみよう*2。
*2
ここでは、FORMに関係ないタグは外してある。
<form action="reauth.cgi" method="post"> <input type="text" name="loginid"> <input type="password" name="password"> <input type="submit" value="OK"> <input type="reset"> </form>
「reauth.cgi」というCGIにIDとPWを送信するらしい。hiddenパラメータはなく、ログイン用CGIと同じ仕組みのようだ。では、再びIDとPWを入力して送信してみる。すると、次のようなレスポンスが返ってきた。
HTTP/1.1 302 Found Server: Apache/2.0.40 (Red Hat Linux) Set-Cookie: ss2=18babf017103310f457c307a0642452f Location: http://www.example.com/userinfo.cgi Content-Type: text/html; charset=iso-8859-1 …… (以下省略)
レスポンスの中に「Set-Cookie」ヘッダが確認できる。どうやら、再認証に成功すると、新たなセッションID(ss2)が発行されるらしい。このセッションIDは、今回の冒頭で説明した「局所的なセッションID」であり、登録情報を参照/変更する機能にアクセスする間、再認証済み状態を維持するためのものである。
このときのクライアントの状態は以下のようになっている。
Locationヘッダにより、userinfo.cgiにリダイレクトされる。リダイレクト後、参照/変更の選択画面が表示される(図3)。
ここで、認証前と認証後で同じCGI(userinfo.cgi)が使われていることに気付くだろうか。全体のセッションID(ss)だけを持っている状態でuserinfo.cgiにアクセスすると再認証画面が表示され、再認証に成功すると別のセッションID(ss2)が発行され、これら2つのセッションIDを持った状態でuserinfo.cgiにアクセスすると、登録情報の参照/変更画面が表示される。
つまり、userinfo.cgiは、Cookie経由で渡される2つのセッションIDをチェックして動作を変えている。
ここまでに分かったことを画面遷移図に書き込んでおこう(図4)。
次に、登録情報を参照/変更する個所を見てみる。最初は、参照用CGIである show_userinfo.cgiにアクセスする。ここへのアクセスもただのリンク(http://www.example.com/show_userinfo.cgi)となっている。
このときのリクエスト、レスポンスは以下のようになる。
・リクエスト
GET /show_userinfo.cgi HTTP/1.0 Cookie: ss=070610200313111508; ss2=4fafa85a68d5f6d34ac640298afe46d8
・レスポンス
HTTP/1.1 200 OK Date: Thu, 06 Nov 2003 01:15:20 GMT Server: Apache/2.0.40 (Red Hat Linux) Content-Type: text/html; charset=euc-jp <html> ……以下の内容で登録されています …… (以下省略)
このリクエストでは、2つのセッションID(ss、ss2)が渡され、レスポンスとして、ユーザーの登録内容が返されている。
さて、ここで以下の点について疑問に思うはずだ。
では順に調べていこう。
セッションID(ss)とセッションID(ss2)の両方がチェックされているのかどうか調べる。セッションID(ss2)がないと登録情報は参照できないのは明らかなので、セッションID(ss)を消して、セッションID(ss2)だけを使ってアクセスしてみる。
このときのリクエスト/レスポンスを以下に示す。
・リクエスト
GET /show_userinfo.cgi HTTP/1.0 Cookie: ss2=4fafa85a68d5f6d34ac640298afe46d8
・レスポンス
HTTP/1.1 200 OK Date: Thu, 06 Nov 2003 04:41:06 GMT Server: Apache/2.0.40 (Red Hat Linux) Set-Cookie: ss=060613200341111509 Content-Type: text/html; charset=euc-jp <html> …… 不正な処理が行われました …… (以下省略)
レスポンスのHTTPヘッダを見ていただくと分かるように、新しいセッションID(ss)が発行されている。どうやら、セッションID(ss)も必須らしい。
次に、セッションIDに対応するユーザーIDは、どちらのセッションIDから取ってきているのか調べる。
このサイトでは、セッションID(ss)を使ってセッション管理を行っている。すなわち、通常は、ユーザーIDはセッションID(ss)から取得されている。では、セッションID(ss2)はどのように使われているのだろうか?
なぜこのようなことを考えるのか、理由を説明しよう。もしセッションID(ss2)にはユーザーIDが結び付けられていないとすると、他人のセッションID(ss2)を使った場合でも登録情報が表示されることになる。つまり、他人のセッションID(ss)さえ奪ってしまえば、後は自分のセッションID(ss2)と組み合わせることで、他人の情報を見ることができてしまうことになる。
さらに、このサイトの場合、前回説明したように、セッションID(ss)は推測可能である。そのため、セッションID(ss)を盗まなくても攻撃できてしまう。
では、早速調べてみよう。この検査ではユーザーIDが2つ必要なので、ユーザー「nakanaka」と、ユーザー「naka」を使うことにする。まずはユーザー「nakanaka」でログインし、再認証を行って、登録情報を表示する画面(http://www.example.com/show_userinfo.cgi)まで進む(図5)。
このときセッションIDは、以下のようにセットされている。
ss=570613200344111511 ss2=4fd6991c1948431535cdd43c4937014b
次に別のブラウザでサイトにアクセスする。Cookie名が重複してしまうのを避けるため、必ず別のブラウザが必要である。別のPCがあるならそれを利用してもよい。
こちらのブラウザではユーザー「naka」でログインする。このときのセッションID(ss)は、以下のようになる。
ss=120614200323111512
再びユーザー「nakanaka」のブラウザを操作する。現在、登録情報を表示する画面(http://www.example.com/show_userinfo.cgi)にアクセスしている状態である。現在の画面をリロードし、Achillesを使ってCookieヘッダ内のセッションID(ss)をユーザー「naka」のものに書き換えてみる。
Cookie: ss=570613200344111511; ss2=4fd6991c1948431535cdd43c4937014b
↓
Cookie: ss=120614200323111512; ss2=4fd6991c1948431535cdd43c4937014b
すると、ユーザー「naka」の登録情報が表示されてしまう(図6)。
攻撃成功である。どうやらセッションID(ss2)にはユーザーIDは結び付けられていないようである。これではセッションIDを2つ使用している意味がない。
前項2が成功したので、セッションID(ss2)が推測可能であろうがなかろうが攻撃者にとっては関係ないのだが、これについても調べておこう。
セッションIDについて調べるには、前回行ったように、セッションIDを収集する作業が必要だ。
セッションID(ss2)を発行するのは少し面倒である。なぜなら、セッションID(ss2)は、再認証後に発行されるものだからだ。セッションID(ss2)を取得するまでの手順を以下に示す。
(1)TOPページ(http://www.example.com/)にアクセスしてログイン前のセッションID(ss)を受け取る。
(2)ログインCGI(http://www.example.com/login.cgi)に、IDとPWをPOSTする。このとき、セッションID(ss)も一緒に送る。
(3)再認証CGI(http://www.example.com/reauth.cgi)に、IDとPWをPOSTする。これに対するレスポンスの中に、セッションID(ss2)が入っている。
上記手順では、必要のないアクセスは省いている。通常のアクセスでは、(2)と(3)の間に、userinfo.cgi にアクセスするが、「再認証が必要です」というメッセージとログインFORMを表示するだけなので、ここでの手順では省略しても問題ない。
このように手順を書き出したのは、自動的にセッションID(ss2)を抜き出すためである。前回同様、これくらいは自動化してしまおう。自動化するスクリプト(Perl版)を以下に示す。
#!/usr/bin/perl # getss2.pl use LWP; use HTTP::Request::Common; my $id = 'nakanaka'; my $pw = 'nakanaka'; my $ua = LWP::UserAgent->new; my $req1 = GET('http://www.example.com/index.cgi'); my $res1 = $ua->request($req1); my $ss = $res1->header('Set-Cookie'); my $req2 = POST('http://www.example.com/login.cgi', [loginid => $id, password => $pw], Cookie => $ss); my $res2 = $ua->request($req2); my $req3 = POST('http://www.example.com/reauth.cgi', [loginid => $id, password => $pw], Cookie => $ss); while(1){ my $res3 = $ua->request($req3); print $res3->header('Set-Cookie'),"\n"; }
このスクリプトの実行結果を以下に示す。
$ perl getss2.plss2=dea7f2e8d1ce4efc817785d869e5e599 ss2=dea7f2e8d1ce4efc817785d869e5e599ss2=79805cfbfce535d7831160370ebda217 ss2=79805cfbfce535d7831160370ebda217 ss2=79805cfbfce535d7831160370ebda217ss2=55499eea17aebf345c970e24db08ca87 ss2=55499eea17aebf345c970e24db08ca87 ss2=55499eea17aebf345c970e24db08ca87ss2=33f568f56edfad2e4311d5c3a97cd161 ss2=33f568f56edfad2e4311d5c3a97cd161ss2=39c3bbee3b6983dbd8abd6b055b1d0c3 ss2=39c3bbee3b6983dbd8abd6b055b1d0c3 ss2=39c3bbee3b6983dbd8abd6b055b1d0c3 ss2=9586f2023ad4e9913ac98b1b60b48cb3 ……
ぱっと見てすぐに気付くのは、同じIDが2、3回連続して出現する場合がある、という特徴だろう。これは、どういうことを意味しているか分かるだろうか?
今回のスクリプトは前回のものと違い、ループ内にsleepを入れていない。sleepを入れないと1秒以内に複数回、セッションIDの生成が行われることになる。セッションIDの生成に使われるパラメータのうち、変動パラメータが時刻だけの場合、このように連続して生成すると、同じIDが生成されてしまうことになる。
ほかにもいくつか特徴があるのだが、残りは読者への課題とする。セッションIDの生成/解析方法はまだまだ話すことはたくさんあるのだが、それはまた別の機会にすることにしよう。
3回にわたってWebアプリケーションの検査について説明を行った。Webアプリケーションの検査ではさまざまな検査項目が存在するが、一番重要なのはセッション管理、ロジックの部分である。この部分がしっかり作られていないと、Webアプリケーションは一気に脆弱なものとなる。開発者はこの部分をしっかり認識して、設計していただきたい。
←「第6回」へ
「第8回」へ→
中村隆之(なかむらたかゆき)
三井物産セキュアディレクション勤務。 セキュリティコンサルタントとして主にWebアプリケーションのセキュリティ検査に従事しており、大手ポータルサイト、オンラインバンキングなどの数多くの 検査実績を持つ。また、セキュアネットワーク及び暗号関連の研究に携わり、大手製造、官公庁、金融機関へのセキュリティシステム導入など数多くの実績を持つ。
主に、不正アクセス監視サービス、セキュリティ検査、セキュリティポリシー策定支援などのサービス提供している。また、セキュリティに関する教育サービスも実施中。
Copyright © ITmedia, Inc. All Rights Reserved.