他社および他組織のWebサイトなどへのポートスキャンおよびデータの取得などの行為で得た情報を侵入などに悪用するか、または同じ目的を持つ第三者に提供した時点で違法となります。ご注意ください。
本稿の内容を検証する場合は、必ず影響を及ぼさない限られた環境下で行って下さい。
また、本稿を利用した行為による問題に関しましては、筆者および株式会社アットマーク・アイティは一切責任を負いかねます。ご了承ください。
前回まではセッションまわりの検査手法について説明した。今回は、ロジック系の脆弱性について説明しよう。
検査対象として使用する画面は、どこのサイトにもある「問い合わせ」画面だ。今回の連載で使用しているデモサイトにもこの画面が存在する。なお、検査対象のCGIはPerlで書かれているとする。
まず初めに問い合わせ画面のCGIがどのような動作をするかについて説明しておこう。問い合わせ画面のURLは、http://www.example.com/toiawase.cgiである。ここにアクセスしたときの画面を、図1に示す(本連載第6回で使用した図19と同様)。
この画面では、問い合わせのタイトル(件名)、氏名、メールアドレス、内容を入力して送信すると、確認画面が表示され、その後に送信される仕組みになっている。それでは、この画面に含まれる脆弱性について説明していこう。
この画面内のFORMを以下に示す(説明に不要な部分は削除してある)。
<form action="toiawase.cgi" method="post"> <input type="text" name="subject"> <input type="text" name="name"> <input type="text" name="email"> <input type="text" name="status" value="confirm"> <input type="hidden" name="to" value="admin@example.com"> <textarea name="naiyou"></textarea> <input type="submit" value="確認"> </form>
このフォームを見ておかしな点にすぐに気付くだろうか。問い合わせ画面から送信されるメールのあて先がhiddenに埋め込まれている。自作のCGIでたまに見掛ける仕様である。このような仕様では、簡単にSPAMの踏み台にされてしまう場合があるため危険である。メールの送信先は、CGI内にハードコーディングしてしまうか、別ファイルから読み込むような仕様にすべきである。
それ以外には「status」という名前のhiddenパラメータが含まれている。このパラメータは、CGIの挙動を変えるためのパラメータである。1つのCGIを使って、(1)入力FORMの表示、(2)確認画面の表示、(3)メール送信、thanks画面の表示、のすべての動作を行わせるには、動作を切り替えるためのパラメータが必要となる。
このサイトではセッション管理を行っているため、セッション変数を使ってCGIのステータスを管理するような仕組みにすれば、パラメータ改ざんの心配がなくなるのだが、ここでは脆弱な仕様にするため、あえてhiddenの中に入れる方式をとっている。なお、このパラメータを使った攻撃については後ほど説明する。
正規表現について簡単に説明しておこう。ユーザーからの入力値を受け付ける際、不正な文字が含まれていないかどうか、もしくは入力が正しいフォーマットであるかどうかのパターンマッチングを行う。このとき、不正な文字や正しいフォーマットを一般的な形式で定義しておく。ここで使うのが正規表現である。正規表現では、「英数字8文字」や「全角カタカナ」「正しいメールアドレス」などを定義することができる。正規表現については、さまざまなWebサイトや書籍で解説されているので、詳細を知りたい読者はそれらを参照していただきたい。
本題に戻ろう。問い合わせCGIでは、問い合わせFORMに入力されたデータを使って、管理者あてに送信するメールを作成し、送信する。問い合わせの画面では、
を入力させる。CGIではこの内容を使って、送信メッセージを作成する。この部分のソース(Perl版)をリスト1に示す(説明に不要な部分は削除してある)。
1: if ($email =~ /\b[-\w.]+@[-\w.]+\.[-\w]+\b/)) { 2: &error('メールアドレスの形式が間違っています'); 3: } 4: 5: $to = "hiddenから受け取ったアドレス"; 6: $title = "ユーザーが入力したタイトル"; 7: $email = "ユーザーが入力したメールアドレス"; 8: $name = "ユーザーが入力した名前"; 9: $question = "ユーザーが入力した質問内容" 10: 11: $message = <<END; 12: To: $to 13: From: $email 14: Subject: 問い合わせ 15: 16: ---------------------------- 17: $title 18: ---------------------------- 19: $name 20: ---------------------------- 21: $question 22: 23: END 24: 25: $message = Jcode->new($message)->h2z->jis; 26: 27: open(MAIL, "| /usr/lib/sendmail -t"); 28: print MAIL $message; 29: close(MAIL);
ユーザーからの入力はすべてチェックする必要があるので、タイトル、名前、質問内容のチェックも行うのだが、ここではその部分は省略してある。
メールアドレスの形式は、RFC821、RFC822、RFC2821で説明されており、正確に表現するともっと長いものになるのだが、本稿では簡単な形式で代用している。
1行目でメールアドレスのチェックを行い、11〜23行目で送信するメッセージを作成し、27〜29行目でメール送信を行っている。まずは1行目に注目してほしい。
if ($email =~ /\b[-\w.]+@[-\w.]+\.[-\w]+\b/)) {
ここで使われている正規表現の表記を表1に示す。
\b | 単語境界 |
---|---|
[…] | …の中の1文字 |
\w | 英数字と「_」 |
. | \n以外の任意の1文字 |
+ | 1文字以上の繰り返し |
表1 正規表現の表記 |
リスト1の説明によると、1行目では、メールアドレスが入った変数$emailが、xxx@xxx.xxxx という形式にマッチするか調べている(マッチしない場合はエラー)ということが分かる。
この正規表現が正しいと仮定すると、ここではメールアドレス以外の入力ができないので攻撃は不可能である、と思われる読者もいるかもしれないが、それは間違いである。この入力チェックでは、任意の文字列が入力可能になってしまうのだ。正規表現を正しく理解していればなんてことはないのだが、このような間違いを犯しているサイトは意外と多い。
どこが間違いなのか説明しよう。このパターンマッチングの意味は、「$emailの中に \b[-\w.]+@[-\w.]+\.[-\w]+\bというパターンが含まれるか」である。つまり、このままでは入力した文字列の一部分にメールアドレスが含まれていればよいことになる。そのため、
naka@example.com "><s>xss</s> ←アドレスの直後には必ず空白が必要
のように入力されると、メールアドレスの入力チェックを通過してしまう。この画面ではメールアドレスの入力チェックだが、それ以外の画面でも、
東京都"><script>alert('xss')</script> 中央区"><script>alert('xss')</script> 03-0000-0000<script>alert('xss')</script>
のようにすることで、入力チェックが回避できる場合がある。この修正方法は簡単で、
if ($email =~ /^[-\w.]+@[-\w.]+\.[-\w]+$/)) {
とすればいい。「^」と「$」は、文字列の行頭と行末を表す。すなわち、入力文字列全体が正規表現と完全にマッチしなければならない。これにより、今回のような攻撃をされても、入力エラーにできる。
次は、先ほどの入力チェックミスが存在する場合に考えられる攻撃について説明する。先ほど説明したように入力すれば、クロスサイトスクリプティング攻撃は簡単に行うことができるが、もっとアプリケーションの動きを利用した攻撃について説明しよう。メールを送信するアプリケーションであるため、任意のメールを任意のあて先に送信するような攻撃を行ってみる。
本稿の最初に示した「hidden内のあて先メールアドレス書き換え」の脆弱性が存在する場合は簡単にSPAMに使用することができるが、メールの本文には問い合わせ用のテンプレートが使われるため、完全に任意のメールを送信できるわけではない。
では、任意のメールを送信する方法について説明していこう。なお、この攻撃は、アプリケーションの実装、および、メールサーバの種類に依存するものであるため、必ず成功するとは限らないので注意してほしい。
すでに問い合わせCGIのソースを出してしまっているので、フォーマットは明らかになっているが、検査時にはまったく分かっていないので、取りあえず自分あてに問い合わせメールを送ってみる。これには、先ほどの「hidden内のあて先メールアドレス書き換え」脆弱性を利用する。
タイトル、名前、メールアドレス、問い合わせ内容を書き込み、hidden内の送信先アドレスを自分(naka@example.com)にして送信すると、次のようなメールが送信されてきた。
To: naka@example.com ≪From: naka@example.com ← メールアドレス≫ Subject: 問い合わせ ---------------------------- ≪質問です ← タイトル≫ ---------------------------- ≪中村 隆之 ← 名前≫ ---------------------------- ≪どうしたらいいですか? ← 質問内容≫
まず初めに、入力した項目がメールのどの部分に入っているか調べる。ここでは、メールアドレスがFromヘッダとして入り、それ以外はメールの本文に含まれていることが分かる。今回の攻撃で使うのは、Fromヘッダに入れられているメールアドレスである。
メールアドレスの部分は、先ほど説明したように、任意の文字列が入力可能である。そのため、改行を含ませることにより、任意のヘッダ、任意の本文が入力可能となる。改行を含ませる方法は次のとおりである。
フォーム内では改行の入力は無理なので、Achilles*1を使って、HTTPリクエストを横取りして書き換える。HTTPで送信されるデータはURLエンコードされるので、改行文字も入力できる。改行文字はCR+LF(ASCIIコードは0x0d、0x0a)を使う。
リクエスト内の
email=naka@example.com
となっている部分を
email=naka@example.com%0d%0aTo:+naka@example.com
と書き換えて送ると、Toヘッダが1つ増え、そのユーザーにもメールが送信される。この方法を知っていれば、hidden内にあて先メールアドレスが入っていなくても、問い合わせメールを自分あてに送信することができる。
同様にして、改行を2つ入れてから、さらに文字列を出力すると、それはメール本文として認識される。
email=naka@example.com%0d%0aTo:+naka@example.com%0d%0aSubject:+hello%0d%0a%0d%0ahello
このように入力した場合に送信されるメールを以下に示す。
To: admin@example.com From: naka@example.com Subject: 問い合わせ …… 本来のメール本文 ……
To: admin@example.com From: naka@example.com To: naka@example.com ← 追加されたToヘッダ Subject: hello ←追加されたSubjectヘッダ hello ← 追加された本文 Subject: 問い合わせ ← メール本文に押し出された本来のSubjectヘッダ …… 本来のメール本文 ……
実は先ほどの改行を入れる操作は、そのままではうまくいかない。なぜならば、問い合わせメールが送信される前に、いったん確認画面が表示されるからである。FORMから入力した値は一度確認画面のhidden内に保持され、確認ボタンを押すまではメール送信されない。そのため、ステータス用パラメータも同時に操作する必要がある。
ステータス用パラメータとは、初めに説明したように、(1)FORM表示、(2)確認画面表示、(3)送信、thanks画面表示、の3つのステータスを管理するためのパラメータであり、「status」という名前で、ほかの入力データと一緒に送信される。
この種のパラメータを使うCGIは、ステータス値を直接操作することにより、指定したステータスに直接アクセスすることが可能となる。これを利用すると、確認画面を表示することなく、直接メールを送信することができる。
問い合わせ画面の最初のFORMには、statusとして「confirm」という値が入っていた。これは「次の画面で確認画面を表示しなさい」という指示である。確認画面のFORMについてはまだ示していなかったが、以下のようになる。
<form action="toiawase.cgi" method="post"> <input type="hidden" name="subject" value="質問です"> <input type="hidden" name="name" value="中村隆之"> <input type="hidden" name="email" value="naka@example.com"> <input type="hidden" name="status" value="send"> <input type="hidden" name="to" value="admin@example.com"> <input type="hidden" name="naiyou" value="どうしたらいいですか?"> <input type="submit" value="送信"> </form>
statusパラメータは「send」という値を持っていることが分かる。すなわち、CGIに対して「status=send」と指定すれば、直接送信してくれる。
今回のポイントをまとめておこう。
これにより、任意のメールが送れるようになる。検査としてはメールが送信できる時点まで検証すれば十分だろう。今回の例では簡単なメール送信だけしか行っていないが、マルチパート形式*2のデータを作成することで、ウイルスなどを添付したメールも送信できるようになる。
今回は、ほとんどのサイトに存在するであろう、問い合わせ画面に含まれるロジック系の脆弱性について説明した。ロジック系の検査では、アプリケーションの動きを把握することが必要である。OSコマンドインジェクションやSQLインジェクションなどのテクニカル系の脆弱性に関しても、やみくもに不正文字列を入力するのではなく、アプリケーションの動きを理解したうえで行うことで、検査の質を向上させることができる。
←「第7回」へ
「第9回」へ→
中村隆之(なかむらたかゆき)
三井物産セキュアディレクション勤務。 セキュリティコンサルタントとして主にWebアプリケーションのセキュリティ検査に従事しており、大手ポータルサイト、オンラインバンキングなどの数多くの 検査実績を持つ。また、セキュアネットワーク及び暗号関連の研究に携わり、大手製造、官公庁、金融機関へのセキュリティシステム導入など数多くの実績を持つ。
主に、不正アクセス監視サービス、セキュリティ検査、セキュリティポリシー策定支援などのサービス提供している。また、セキュリティに関する教育サービスも実施中。
Copyright © ITmedia, Inc. All Rights Reserved.