あなたは悪い道から救い出され、暴言をはく者を免れることができる。 | |
旧約聖書 箴言 2 章 12 節 |
入力には、信頼できないユーザからのものもあります。そこで、使用する前にそれら を検証(選別)する必要があります。 まず何が正しいかを定義して、その定義にマッチしないものすべてを拒否するように しなければいけません。 その逆の定義の仕方をしてはいけません(何が不正かを定義し、それらを拒否する)。 なぜなら、重大なケースをうっかり定義し忘れてしまうかもしれないからです。
ですが、検証コードが完全なのかを確認するために、テスト用(たいていは頭の中 で実行)で「不正な」値を定義するのは良いことです。 私は入力フィルタを設定した時には、頭の中でフィルタを攻撃してみて、不正な値 がフィルタを通り抜けないかを見てみます。 入力内容にもよりますが、ここでは代表的ないくつかの「不正な値」の例をあげて みます。これらは入力時にフィルタで防御する必要がある値です。 空文字や「.」、「..」、「../」、「/」や「.」ではじまる文字列や「/」もしくは 「&」を含む文字列、すべての制御文字(特に NIL や改行)もしくは「ハイビット」 の文字(特に十進数で 254 と 255)がそれです。 繰り返しますが、コードは「悪い」値でチェックすべきではありません。頭の中で あなたが書いたパタンが容赦なく入力を制限して、正しい値だけを通すのか どうかを確かめるためです。 もしそのパタンで十分に制限していなければ、注意深くパタンを再調査して、 他の問題がないか確認する必要があります。
最大文字数(適切なら最小文字数も)を制限して、文字数を超えても制御不能に ならないようにしてください (バッファオーバーフローについての詳細は Chapter 5 を見てください)。
ここではデータタイプとしてよく使われるものをいくつか挙げます。信頼できない ユーザからのデータを利用する前に、必ず検証するようにしてください。
文字列に対しては、正しい文字やパタン(たとえば、正規表現として)を識別し、 それに沿わないものすべてを拒否してください。 文字列に制御文字(特に改行や NIL)やメタキャラクタ(特にシェルのメタキャラクタ) があると特殊な問題が発生します。入力があったら、速やかにメタキャラクタを 「エスケープ」するのが最善です。入力が間違って渡らないようにするためです。 CERT はそれ以上に、[CERT 1998、CMU 1998] にあるエスケープする必要のない文字 のリストに載っていない文字すべてをエスケープするように推奨しています。 メタキャラクタについての詳しい情報は、Section 7.3 を見てください。
数字すべてに対して、許容できる最小値(たいていはゼロ)と最大値を設けてください。
電子メールのアドレスを完全にチェックするのは、現実的にとても困難です。 というのも、すべてのケースを真面目にサポートしようとすると、アドレスの中には 正しい形式ではあるものの、非常に複雑な検証を必要とするアドレスが存在するから です。もしそのようなチェックが必要なら、詳細は mailaddr(7)と IETF RFC 822 [RFC 822]を見てください。 たいていは、「一般的な」インターネット・アドレス形式だけを単純に許可すれば よいでしょう。 【訳注:IETFは、Internet Engineering Task Force の略称で、インターネットに 関連する技術の標準化を進めるために設立された団体です。ここが発行する文書が RFC(Requests For Comment)です】
ファイル名をチェックしてください。普通、信頼できないユーザからは、「..」 (上位ディレクトリ)という値を正しいものとして受け取りたくないでしょう。 しかし、それは環境に依存しています。 また、許可した文字だけを載せてもよいかもしれません。特に改行(問題を起こす 可能性があります)は、問題なければ削除してください。 ファイル名においては、ディレクトリにおけるどんな変更も禁止するのが最善の策 です。たとえば、「/」を正しい文字として扱わない等。 「glob」をサポートしてはいけません。つまりファイル名を拡張するような 「*」や「?」、「[」(「]」に対応)」さらに「{」(}に対応)」等です。 たとえば、「ls *.png」というコマンドは「*.png」を全 PNG ファイルのリストに glob します。 C の fopen(3) コマンド(たとえば)は、glob しませんが、コマンドシェルは、 デフォルトで glob します。また C では(たとえば)glob(3)を使って glob 機能を 利用できます。 glob が必要なければ、 glob しないシステムコール(たとえば fopen(3)だけをできる だけ使用するか、無効にしてください(たとえばシェルで glob する文字をエスケープ する)。 glob を許可するなら、細心の注意を払ってください。 glob は便利に使えますが、glob を複雑にするとコンピュータにかなりの負荷を かけることになります。 たとえば、ftp サーバには glob 命令をいくつか要求すると、いとも簡単に マシン全体でサービス拒否攻撃状態になるものもあります。
ftp> ls */../*/../*/../*/../*/../*/../*/../*/../*/../*/../*/../*/../* |
URI(URL を含む)が妥当なのか、チェックしなければいけません。 ある URI を直接操作するなら(つまり、Web サーバや Web サーバもどきの プログラムを実装していて、要求されるデータが URL の場合)、URI が正しいか どうかを確認しなければなりません。また URI で特に注意を払うケースは、 ドキュメントルート(サーバが返すファイルシステム領域)を「回避」しようと するものです。 ドキュメントルートを回避する最も一般的な方法は、「..」やシンボリック リンクを経由する方法です。したがって大半のサーバは、どんな「..」ディレクトリ もチェックしており、シンボリックリンクは特に指示がない限りは無視します。 また、エンコード(URL エンコードや UTF-8 エンコード)してあるものは、まず デコードすることを忘れないでください。でないとエンコードされた「..」が すり抜けてしまいます。 URI は UTF-8 エンコードが入ることを前提としていませんので、ハイビット文字が 入った URI すべてを拒否するのが最も安全です。
データとして URI(URL)を扱うシステムの実装をしているなら、簡単にできるとは夢々 思わないでください。悪意あるユーザが他のユーザに迷惑をかけるような URI を 入れ込むことが絶対ないようにしなければいけません。 さらに詳しい情報は Section 4.10.4 を見てください。
クッキーで値を受けとった時には、利用しているクッキーがどんなものであっても、 ドメイン値が予期した値であることを必ずチェックしてください。さもないと、 (おそらくクラックされた)関連サイトが偽のクッキーを入れ込んでしまう可能性が あります。 ここでは、このIETF RFC 2965 で書いてある例をあげます。このチェックを しくじるとどのような問題が生じるかについて記述してあります。
ユーザ側が victim.cracker.edu にリクエストを出し、クッキーが session_id="1234" と返ってきて、デフォルトのドメインを victim.cracker.edu に設定します。
ユーザ側は spoof.cracker.edu にリクエストを出し、クッキーが Domain=".cracker.edu" で session-id="1111" と返ってきます。
再度ユーザ側は victim.cracker.edu にリクエストを出し、下記を渡します。
Cookie: $Version="1"; session_id="1234", $Version="1"; session_id="1111"; $Domain=".cracker.edu" |
問題を解決できないなら、 正しい文字パタンには、プログラム内部や最終的な出力に対して特別な意味を 持つ文字もしくは文字列を含めてはいけません。
ある文字の連続が、プログラムの内部に持っている書式に対して、特別な意味を持つ 場合があるかもしれません。 たとえば、保存するデータに区切りのある文字列を使うなら(内部でも外部でも)、 区切り文字をデータ値とすることを必ず禁じてください。 テキストファイルに保存してあるデータに、カンマ(,)やコロン(:)を区切りとして 使っているプログラムはたくさんあります。 入力に区切り文字が入っていて、プログラムがそれに対処(すなわちそれを阻むか、 何らかの方法でエンコードする)していなければ、問題が発生するかもしれません。 他の文字でも、これらの問題が発生する場合がよくあります。 それは、他の文字の中にシングルもしくはダブル・クォーテーション(文字列を囲む のに使用)や小なり記号「<」(SGML や XML、HTML ではタグの開始を示す識別に 使われています。これらのフォーマットでデータを保存する場合、この記号は 重要です)が入っているケースです。 大半のデータフォーマットは、エスケープシーケンスを用意して、このようなケース に対処しています。そのエスケープシーケンスを使うか、入力データをフィルタして ください。
ユーザに対してある文字の連続が戻される場合に、特別な意味を持つケースがあり ます。 一般的な例として、HTML のタグを入力として認める場合、後になってそれを他の ユーザにポストすることがあります(たとえば、ゲストブックや「読者のコメント」 コーナー)。 しかし、この問題は広く影響を及ぼしています。 この話題についてさらに全体的な議論は、 Section 6.15 を、HTML のフィルタリングに ついて特化した議論は Section 4.10 を見てください。
これらのテストは 1 ヶ所で集中して行ってください。そうすれば、そのテストが 正しいかどうか、後になって簡単に調査できます。
正しい入力をチェックするテストが、予定した通り確実に動作するようにしてくだ さい。別のプログラムが使う入力(ファイル名や電子メールアドレス、URL 等)を チェックする場合には特に重要です。 これらのプログラムは、見落としがちな間違いを抱えていることが多く、いわゆる 「代理人問題」(データを実際に使用するプログラムとチェックするプログラム間で 前提条件が異なっているケース)が発生します。 適切な規準があるなら、それを見てください。あわせて、そのプログラムが、拡張 機能を持っていないかどうかの調査もしてください。拡張機能は知っておく必要が あります。
ユーザの入力を解析している間は、一時的に特権すべてを落とすというのは良い 考えです。また独立したプロセスを作成するのも、同じく良い考えです (解析を行う場合は常に特権を落とし、他のプロセスが解析の要求に対してセキュ リティ上のチェックを行う)。 このケースがとりわけ当てはまるのは、解析作業が複雑である場合(たとえば、lex や yacc といったツールを使う)や、プログラミング言語がバッファオーバーフローを 防げない場合です(たとえば、C や C++)。 特権を最小限にする方法については Section 6.4 を 見てください。
セキュリティ上の判断を行う際にデータを使用する時には(たとえば「このユーザを 通過させなさい」)、必ず信頼できる経路を使ってください。 たとえば、公開されたインターネット上では、マシンの IP アドレスやポート 番号だけにユーザの認証を任せてはいけません。というのは、この情報を(もしか すると悪意を持った)ユーザが設定できてしまう環境が大多数だからです。 詳しい情報は Section 6.11 を見てください。
下記のサブセクションでは、プログラムに対するさまざまな入力について論じます。 環境変数や umask 値等、プロセスの状態を含む入力には注意が必要です。 入力すべてが信頼できないユーザによってコントロールされているわけではないので、 これから論じる入力だけを気にかければ OK です。