狙われるWebアプリケーション【第2回】


京セラコミュニケーションシステム
セキュリティ事業部 副事業部長
徳丸 浩(とくまる ひろし)

 クロスサイト・スクリプティングという言葉は元々,WebアプリケーションのHTMLエンコード漏れなどを利用することによって第三者にJavaScriptを実行させる手法を指す。広義では,HTMLのエンコードによる画面改変などを含むこともある。

 前回述べたように,クロスサイト・スクリプティングのぜい弱性はWebアプリケーションに見付かるぜい弱性の半分以上を占める。数年前から指摘されているにもかかわらず,一向になくならない。その理由として,クロスサイト・スクリプティング対策あるいはHTMLエンコード注1)に対する「神話」があり,正しい対策の普及を遅らせているように思う。その「神話」の数々について説明しよう。

注1)実体参照(entity reference)というのが正式だが,あまり普及していない用語なので,HTMLエンコードという用語を用いる

「すべからくHTMLエンコードすべし」が鉄則

 HTMLエンコードというのは,例えば「&」「<」「>」「"」といった,HTMLとして特殊な意味を持つ文字(特殊文字またはメタ文字)を,意味を持たない別の文字列に置換することである。前述の特殊文字なら,「&amp;」「&lt;」「&gt;」「&quot;」に置換する。前回も紹介したエスケープ処理である。この処理を怠ると,メタ文字として扱われるデータの入力を許すことになる。これが,クロスサイト・スクリプティングの原理だ。

 JavaScript実行による典型的な攻撃は,第三者のCookieを盗み出して行うセッション・ハイジャックである。米国のYahoo!メールで発生したYamannerワーム(参考:「Yahoo!メールのウィルスが悪用したのは『XSS脆弱性』,サイト管理者は注意を」---専門家が警告) のように,自ら増殖機能を持つような”高度な応用”も現れ始めている。さらに,タグ文字のエスケープ漏れを悪用して偽のログイン画面や個人情報入力画面を作り,ログインIDやパスワードといった個人情報を盗み出すフィッシング手口も現れてきている(参考:「XSS脆弱性は危険,Cookieを盗まれるだけでは済まない」専門家が注意喚起)。

 いずれの場合も対策は同じ。原因がHTMLエンコードの漏れなのだから,すべての項目についてHTMLエンコードすることが正しい対応である。この場合の注意は次の通り。細かいことをいちいち考えずに,可変となる変数や式などの表示に関してはすべからくHTMLエンコードすべしというのが原則である。表1に,変換すべきメタ文字と変換後の文字列を示す。

  • 変数や式など可変部の表示はすべてHTMLエンコードする
  • すべてのinputフィールドのvalue属性などもHTMLエンコードする
  • タグ文字だけでなく,クォート文字もエンコードする
  • アプリケーション要件上特殊文字の入りえないところもエンコードする
  • エンコード処理は,表示の直前で行う

    表1 HTMLのエスケープ処理

    メタ文字変換後
    '&#39;
    "&quot;
    &&amp;
    <&lt;
    >&gt;

     アプリケーションの要件から見てエンコードが不要なフィールドまでHTMLエンコードするのは無駄に思えるかもしれないが,そうではない。HTMLエンコードに要する労力はわずかなものだし,すべての項目をエンコードすることによりチェックなどが容易になる。すべてをエンコードすると決めたほうが簡明でミスが少なくなるわけだ。逆に,ある部分はエンコードし,別の部分はエンコードしないという状態になると,かえって煩わしい。このため,Webアプリケーションを開発する際には,フレームワークなどに自動エンコードの機能を組み込むことが望ましい。

     このように,クロスサイト・スクリプティング対策の原則は単純そのものであり,難しいところは何もない。しかし実際には,多くのWebアプリケーション開発者に「HTMLエンコードの神話」が根付いており,このことが正しい対策の普及を遅らせているように思う。具体的には,

  • 神話1:HiddenフィールドのValue属性などはエンコードする必要がない
  • 神話2:クォート文字はエンコードする必要がない
  • 神話3:Value属性値にHTMLエンコードすると,アプリケーション側でデコード処理が必要になる
  • 神話4:二重にエンコードするのが心配だ
  • 神話5:出力よりも入力の方が対応箇所が少なくて楽だ
  • 神話6:HTMLエンコードだけではクロスサイト・スクリプティングは完全には防御できない
    といったものである。

    神話1】HiddenフィールドのValue属性などはエンコードする必要がない
     もっとも古典的な「神話」はこれだろう。事実は,HiddenフィールドのValue属性値は変更できる注2)し,GETパラメータやPOSTパラメータを改変したリクエストを別のユーザーに実行させることも可能だ注3)。このため,Hiddenフィールドだけではなく,ラジオボタン,チェックボックス,セレクトボックスなどのValue属性も漏れなくエンコードしなければならない。

    注2)例えばFirefoxのアドオン・ツールには,Hiddenフィールドの値を変更するための便利なツールがある。
    注3)GETの場合はURLを起動させるフィッシング的な手法,POSTの場合は,HTMLメールを利用するか,いったん「わな」のページに誘導してそこからJavaScriptによりPOSTする。

    神話2】クォート文字はエンコードする必要がない
     IPAが刊行している「安全なウェブサイトの作り方 改訂第2版」によると, 『HTMLタグを出力する場合は,その属性値を必ず「"」(ダブルクォート)で括るようにします。そして,「"」で括られた属性値に含まれる「"」を,HTMLエンティティ文字「&quot;」にエスケープします。』 となっている。これは,タグの属性値以外のところでは「"」をエスケープしてなくてもよいように読める。実際,技術的には正しい指摘である。ただ筆者は,予防的処置として,どのような場合でも「"」のエスケープをお勧めする。

     その理由としては,

  • 「"」のエスケープする・しないを区別することが煩わしく,ミスの原因となる
  • 「"」の余分なエスケープによる誤表示などの副作用はない
  • 「"」の余分なエスケープによるオーバーヘッドは軽微で無視できる
  • 文字化けを利用したクロスサイト・スクリプティング攻撃に対して,より頑丈になる
    といったことが挙げられる。文字化けを利用したクロスサイト・スクリプティングとは次のようなものである。まず,次のような簡単な入力フォームのCGI(common gateway interface)断片を想定する(言語はPerlを想定)。

    <input name="foo" value="$v1">$p1 ...

    この例で,パラメータ$v1と$p1にそれぞれ,

    $v1文字コード0x81の文字
    $p1"onmouseover=alert("xss");


    写真1 文字化けによるクロスサイト・スクリプティングの例
    [画像のクリックで拡大表示]

    という値をセットしてみる。すると,写真1のような画面が表示され,テキストボックスにマウス・カーソルがかかると,自動的にJavaScriptが実行される。写真はFirefoxで示したが,Internet Explorer(IE)やOperaでも同様の動作を確認している。また,文字コードはシフトJISがもっとも発生しやすいが,IEの場合,EUCでも同様の動作となった。

     なぜこのような現象が発生するのか。この例では,0x81というデータはシフトJISコードの1バイト目のグループに属するため,後続のダブルクォート「"」がシフトJISコードの2バイト目と解釈されている。このため,Value属性が「閉じられない状態」のままになり,表示項目中の「onmouseover」という文字列が,onmouseoverイベント・ハンドラと解釈され,JavaScriptを実行してしまう。この際,変数$p1のダブルクォート「"」がエスケープされていれば,このような事態は避けられる。

     この問題が発生する根本原因は何か?一つには,0x81という「単体では不正な文字」をそのまま表示したアプリケーション側の問題と言える。また,0x81とダブルクォート(0x22)の組み合わせはシフトJISとしてもEUCとしても該当文字はない注4)ことを考えると,「あり得ない文字」の取り扱いに関するブラウザの問題とも言える。しかし,このような問題が現実に発生し得ること,クォート文字のHTMLエンコードにより予防が可能であることなどから,「どんな場合でもクォート文字をエンコードする」というルールにしておくのが予防的プログラミングとして安全である。

    注4)後続文字がシングルクォート「'」(0x27)の場合も同様である。

    神話3】Value属性値にHTMLエンコードすると,アプリケーション側でデコード処理が必要になる
     ネット上のブログなどを読んでいると,INPUTタグやOPTIONタグなどでValue属性値をHTMLエンコードすることについて,少なからず誤解が見受けられる。Value属性の値を利用するのはCGIプログラムやJavaScriptであり,これらのプログラムにはHTMLエンコードした値がそのまま渡されるので,アプリケーションやスクリプト側で元に戻して(デコードして)やる必要があるというのである。  しかし,これは文字通り誤解である。理由は簡単。Value値のHTMLエンコードは

  • CGIのGETあるいはPOSTのパラメータとして渡されるとき(通常URLエンコードされて送出される)
  • JavaScriptのスクリプトから参照するとき のタイミングで既にデコードされているからである。 Value値のHTMLエンコードには副作用はない。

     なお,ブログやSNSなど最近のWeb 2.0的なアプリケーションの普及により,テキスト入力フィールド内でタグ文字(「<」や「>」)を入力する機会が増えてきているが,そのような場合にこそ,Value属性値のエンコード処理は重要である。そうしなければ,タグ文字類が正確に表示されないケースがあるからである。

    神話4】二重にエンコードするのが心配だ
     HTMLエンコードする際に,誤って二重にエンコードすると表示がおかしくなる。これは神話ではない。事実その通りである。  しかし,このようなことが発生するのは,当然ながらHTMLエンコード自体が悪いのではなく,エンコードのやり方が悪いのである。エンコードのやり方を統一し,表示の直前に行うようにすれば,二重エンコードというミスはたいてい避けられるはずである。それでも混入した場合は,テストなどで検出して修正するしかない。それはほかのバグと同じことであって,HTMLエンコードを否定する理由にはならない。

    神話5】出力よりも入力の方が対応個所が少なくて楽だ
     確かに一般的なWebアプリケーションでは,出力時よりも入力時にチェックした方が該当個所をコンパクトにまとめやすい。しかし入力時チェックではアプリケーション要件を満たさないことは前回の記事で説明した通りである。開発効率の問題は,フレームワークなどで解決すべきものであって,間違った方法を採用することで効率を高めるのは不適切であろう。

    神話6】HTMLエンコードだけではクロスサイト・スクリプティングは完全には防御できない
     これもまた事実である。Webアプリケーションの設計・開発者はHTMLエンコードで対策できないケースについても知っておく必要がある。具体例とその対策については,次回改めて解説することにする。