安全が必要なプログラムには、信頼できないユーザ(攻撃者)からデータを受け取ったり、 そのデータを別のユーザ・アプリケーション(犠牲者)に渡すものがあります。 安全が必要なプログラムが犠牲者を保護してあげなければ、犠牲者となるアプリ ケーション(たとえば Web アプリケーション)は、そのデータを処理して、犠牲者に 害を及ぼします。 これは、HTML や XML を利用した Web アプリケーションではとりわけ良く見られる 問題で、「クロスサイト・スクリプティング」や「悪意ある HTML タグ」、 「悪意あるコンテンツ」といういくつかの呼び方で通っています。 このドキュメントでは、この問題を「サイトにまたがった悪意あるコンテンツ」と 呼びます。スクリプトや HTML に問題がしぼられているわけではなく、サイトに またがる性質が問題の根本だからです。 この問題は Web アプリケーションに限ったものではありませんが、Web アプリ ケーションにとっては特別に問題となるので、これからこのドキュメントでは Web アプリケーションに焦点を当てて論じていきます。 まもなく説明して行きますが、攻撃者は時に犠牲者がデータを安全が必要な プログラムに対して送りつけるように仕向ける場合があります。そこで安全が必要 なプログラムは、犠牲者自身を守ってあげなければいけません。
まずは単純な例からはじめましょう。 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)。
基本的には、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を参照してください)。
ここでは、さまざまな環境における特殊文字を載せます。 (この一覧を作成した CERT に感謝します)
ブロックレベルの要素(たとえば、HTML や XML のブロックに含まれる テキストのパラグラフに登場する)
「<」 は、タグを開始するという意味で特殊です。
「&」 は、文字エンティティがはじまるという意味で特殊です。
"「gt;」 は、ブラウザの中に特別扱いをするものがある、という ことで特殊です。そのページの著者が、本当は開始の 「<" を置くつもりでだった のに、間違って省いてしまったという前提です。
属性値について
二重引用符で囲まれた属性値において、二重引用符は属性値の終端 の印ということで特殊です。
一重引用符で囲まれた属性値において、一重引用符は属性値の終端 の印ということで特殊です。 XML では一重引用符は正規ではないことに注意してください。一重引用符は使わない よう推奨します。
何も引用符がついていない属性値では、スペースやタブといった 空白文字が特別扱いになります。 XML では正規のものでないだけでなく、さらに他の文字を特別 扱いにしてしまうことに注意してください。 つまり、動的に値を生成したものを使っているなら、引用符が付かない属性を使用 するのは賛成できません。
「&」は、文字エンティティの始点になっているため、属性を 結合するのに使用するということで特殊です。
たとえば URL を例にとると、ある検索エンジンは検索結果のページを表示 し、そのページ内のリンクをユーザがクリックして検索を再実行できるとします。 この機能は、検索クエリを URL 内に符号化することで実現しています。 実行が完了すると、追加の特殊文字が入り込みます。
スペースやタブ、改行は、URL の終端の印となるので特殊です。
「&」は、文字エンティティのはじまりであったり、CGI のパラメタ のはじまりであったりするということで特殊です。
ASCII ではない文字(ISO-8859-1 符号で 128 以上)は URL では認められて いません。したがって URL 内の ASCII ではない文字は特殊です。
「%」は入力でフィルタされなければいけません。 HTTP のエスケープ シーケンスで符号化されたパラメタが何であっても、 サーバ側で使っているコードへ復号しなければいけません。 入力が「%68%65%6C%6C%6F」なら、それをフィルタすると Web サイトのページ では「hello」になります。
<SCRIPT> と </SCRIPT> で囲まれた部分にあるセミコロンや 括弧、中括弧、改行は、既存のスクリプト・タグに直接テキストが挿入 できる状況下ではフィルタすべきです。
サーバ側のスクリプトで入力にある感嘆符(!)を出力で二重引用符(") に変換するなら、さらに追加でフィルタが必要になります。
一般的に HTML や XML においては、アンパサンド(&)は特殊扱いです。
これらの特殊文字を扱う方法の一つに、単に特殊文字を削除してしまうという 手があります(通常は入力と出力の間に)。
既に正しい文字を得るために入力を検証しているなら(そうすべきです)、正しい 文字の一覧から特殊文字を取り除くのは簡単です。 ここに、Perl で書いたフィルタを載せておきます。このフィルタは正式な文字 だけを受け付け、空白以外のどんな特殊文字も受け付けませんので、引用属性 のような部分で使用するのにかなり有効です。
# Accept only legal characters: $summary =~ tr/A-Za-z0-9\ \.\://dc; |
しかし、本当に最低限の文字だけを取り除きたいなら、 その文字だけを削除するサブルーチンを作っても良いでしょう。
sub remove_special_chars { local($s) = @_; $s =~ s/[\<\>\"\'\%\;\(\)\&\+]//g; return $s; } # Sample use: $data = &remove_special_chars($data); |
特殊文字を削除する別の方法に、特殊文字を符号化して特殊な意味を持たなく してしまうやり方があります。 この方法は、文字にフィルタをかける方法に対して若干長所があり、特にデータを 取りこぼさない点が優れています。 ユーザから見て、フィルタする過程でデータが「めちゃくちゃ」になるなら、 少なくとも符号化をしておけば、元々送られてきたデータの再構成が可能に なります。
HTML や XML、SGML は皆、アンパサンド(「&」)を本文中で何らかの符号化が はじまる文字として使っています。この符号化は「HTML エンコード」とよく言われて います。 これらの文字を符号化するには、ただご自分の環境で特殊文字に変換してやるだけ です。普通これは、 「<」が「<」、 「>」が「>」、 「&」が「&」、 「"」が「"」となります。 上記で注意しなければならないのは、理屈上は「>」はここで挙げる必要はないの ですが、ブラウザには「>」が悪さをしてしまうものがあるので(「<」 を入れてしまう)、ここで挙げることにしました。 二重引用符も多少面倒で、「"」を使う必要があるのは属性内部だけですが、 古いブラウザにはきちんと表示できないものもあります。 さらに複雑になってもかまわないなら、必要に応じて「"」を符号化しても かまいませんが、単純に符号化する方がやさしいので、ユーザにブラウザのバージョン アップをお願いしてください。
この 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 と同じ文字値です)。 つまり И はキリル文字の大文字の「I」です。 SGML 規格(ISO 8879)では 16 進数系をサポートしていませんので、出力には 10 進数 系を使用するようにお薦めします。 また SGML の仕様では、ある環境下で後続のセミコロンの省略を認めています。 実際に、システムの多くでは扱えません。したがって、常にセミコロンを後ろに付ける ようにしてください。
文字エンティティへの参照は、同じようなものですが、数字の かわりに覚えやすい名前を使っています。 たとえば、「<」は < を表わします。 HTML を書いているなら、 HTML 仕様 に覚えやすい名前をすべて 一覧にしてありますので、見てください。
URI は独自に符号化の仕組みを用意しています。通常はそれを 「URL エンコーディング」と呼んでいます。 この系では、URL 中に認められない文字をパーセント記号の後に 2 桁の 16 進数値 を使って表示します。 ISO 10646(Unicode)を扱うため、まずコードを UTF-8 に変換してから符号化する ことが推奨されています。 URI の検証については Section 4.10.4 でさらに論じています ので、見てください。