7.3. メタキャラクタを扱う

システムの多く、たとえばコマンドライン・シェルや SQL インタプリタには、 「メタキャラクタ」が存在します。つまり入力中のある文字が、データとして 解釈されません。 そのような文字はコマンドであったり、コマンドや他のデータからあるデータを 区別するための識別子であったりします。 使用しているシステムのインタフェースに言語仕様があるなら、きっとメタキャラクタ が含まれているはずです。 プログラムが他のシステムを実行するようになっていて、攻撃者がそのような メタキャラクタを入れ込めるなら、攻撃者は完全にプログラムをコントロールして しまう、というのがお決まりの結末です。

メタキャラクタの問題で最も広範囲に渡っているのは、シェルのメタキャラクタです。 標準的な Unix ライクなコマンド・シェル(/bin/sh 内蔵の)は、かなりの数の文字を 特別扱いします。 これらの文字がシェルに渡ると、エスケープしていない限り、特別に解釈されます。 この事実を悪用して、プログラムがおかしくさせられてきました。 WWW Security FAQ [Stein 1999, Q37]によれば、そのようなメタキャラクタは下記の ものです。
& ; ` ' \ " | * ? ~ < > ^ ( ) [ ] { } $ \n \r

注意すべきは、タブや空白文字をエスケープしたい場面がいろいろあるのでは、 という点です。それら(と改行)はパラメタのデフォルトのセパレタだからです。 セパレタの値は IFS 環境変数を設定して変更できますが、この変数の出所が信用 できないなら、その値を破棄するか、環境変数を処理する過程で何らかの方法で リセットしてください。

あいにく、完全なリストは現実には存在しません。ここでは疑わしいと思われる 文字を他にもいくつか挙げておきます。

シェルのメタキャラクタの影響が著しく広範囲になってしまっているのは、いくつ かの重要なライブラリ・コール、たとえば popen(3)や system(3)、がコマンドシェル を呼び出して実行するからです。つまり、シェルのメタキャラクタからも影響を受けて います。 同様に execlp(3)や execvp(3)もシェルを呼び出す仕組みになっています。 popen(3)や system(3)、execlp(3)、execvp(3)をまったく使用しないように提案して いるガイドラインが多く、プロセスを生成する場合には execve(3)を C 言語から 直接呼び出すように提案しています [Galvin 1998b]。 とにかく、execve(3)が使えるなら、system(3)の使用を避けてください。system(3)は シェルを使って文字を展開しますので、危険がさらに広がります。 同様に Perl や シェルのバッククォート(`)もコマンドシェルを呼び出せます。 Perl についての詳しい情報は Section 9.2を見てください。

SQL にもメタキャラクタがありますので、同じような問題が SQL の呼び出しにも 存在しています。 SPI Dynamic's paper ``SQL Injection: Are your Web Applications Vulnerable?'' を見てください。 この点についてさらに論じていきましょう。 Chapter 4で論じた通り、非常に限定的なパタンを定義して、 パタンにマッチした入力だけを許可するようにしてください。パタンを ^[0-9]$ もしくは ^[0-9A-Za-z]*$ に制限してあるなら、問題は起こらないでしょう。 SQL メタキャラクタが入ったデータを扱う必要があるなら、それを何か別の符号に 変換してから(できるだけ早いうちに)、保存してください。たとえば、HTML エンコードのように(この場合は、アンパサンド文字を符号化してやる必要があります)。 また引用符でユーザの入力すべてを囲んでください。たとえデータが数字であっても です。そうすれば、空白や他の種類のデータは危険ではなくなります。

これらの文字の 1 つでも忘れると災難を被るかもしれません。たとえば、プログラム の多くは、バックスラッシュをシェルのメタキャラクタとして削除してしまいます [rfp 1999]。 Chapter 4で論じたように、推奨する解決方法は、入力されたらすぐ、 それらの文字をともかくエスケープする方法です。 しかし、はるかに適切な解決方法は、どの文字を許可するのかを自分で特定する 方法です。そして、それらの文字だけを許可するようにフィルタをかけます。

プログラムには、人間とやり取りするべく設計されたものがたくさんあります。 そのようなプログラムは、「特別な」行為を実現する「エスケープ」コードが あります。 もっと一般的(で危険)なエスケープコードの 1 つに、コマンドラインを立ち上げる というものがあります。 このような「エスケープ」コマンドが絶対無いようにしてください (さもなければ、そのコマンドが確実に安全であるようにしてください)。 たとえば、コマンドライン指向のメール・プログラム(mail やmailx のような)では、 チルダ(~)をエスケープキャラクタとして使っています。チルダは多量のコマンド を送る場合に使われてきました。 明らかに無害なコマンド、たとえば、「mail admin < file-from-user」 が、結果的に任意のプログラムを実行するのに利用できます。 vi や emacs、ed のような対話形式のプログラムは、「エスケープ」する仕組みを 持っていて、ユーザがプログラム実行中に任意のシェル・コマンドを走らせられます。 呼び出すプログラムのドキュメントをいつも調べて、エスケープする仕組みがないか 調査してください。 他のプログラムを呼び出すなら、利用したいものだけを呼び出すようにするのが適切 です。Section 7.4 を見てください。

エスケープコードを回避する問題は、対象範囲が低レベルなハードウェアの部品や それをエミュレートするものにまで広がります。 モデムにはたいてい「Hayes」と呼ばれている命令セットが実装してあります。 この命令セットが有効になっていると、遅延を発生させる「+++」というフレーズ やそれにともなう別の遅延によって、そのコマンドに続くテキストがモデムに対する コマンドと解釈されます。 これはサービス拒否攻撃の実行に利用できますし(「ATH0」を送ることで、モデムを ハングアップさせます)、ユーザを別の所に接続させることさえ可能です(巧妙な 攻撃者なら攻撃者が制御しているマシンを経由するように、ユーザの接続の経路を 変えてしまいます)。 ケースをモデムに限定すれば、対処するのは簡単です(たとえば、モデムの初期化 文字列「ATS2-255」を加えておきます)。しかしまだ一般的な問題は残っています。 低レベルな部品やそのエミュレータを制御しているなら、必ずそれらに組み込んで あるエスケープコードを無効にするか、対策を施してください。

「端末」インタフェースでは、既になくなって久しい VT100 のような昔の端末の エスケープコードを実装しているケースが多くあります。 これらコードはとても便利で、端末のインタフェースを使って、たとえば文字を太く したり、フォントの色を変えたり、特定の位置に移動したりできます。 しかし、直接端末のスクリーンに任意の信頼できないデータの送出を認めてはいけ ません。というのは、コードによっては重大な問題を引き起こすものがあるからです。 システムには、キーの割り当てを変更できるものもあります(たとえば、ユーザが 「Enter」もしくはファンクションキーを押すことで、望みのコマンドを送って実行 できます)。 中には、コードを送ってスクリーンをクリアしたり、犠牲者となる人に実行させたい コマンドを表示できたりするものもあります。表示させておいて、画面を「元に戻す」 命令を送って、キーが押されるのを待たずに攻撃者が選んだ命令を実行させてしまい ます。 これは通常「ページモード・バッファリング」という機能を使って実現しています。 このセキュリティ上の問題は、仮想 tty(デバイスファイルとして提供されていて、 通常は /dev にあります)が所有者にだけ書き込みが可能で、他には誰も書き込めない ようにすべきである理由になっています。決して「その他の書き込み」パーミッション を設定してあってはいけません。また、ユーザがグループ(つまり「ユーザプライベート グループ」という手法)のメンバーだけでないなら、「グループによる書き込み」 パーミッションも端末に対してかけるべきではありません[Filipski 1986]。 ユーザに対してデータを(擬似)端末で表示しているなら、安全を確認していない限り すべての制御文字(32 よりも小さい値の文字)をフィルタして、ユーザに戻すデータ から取り除く必要があります。 最悪の状況では、タブや改行(おそらく復帰改行も)を安全とした上で、残りすべて を排除します。 ハイビットが立っている文字(つまり 127 より大きい値)を扱うにはテクニック を要します。古いシステムには、ビットが立っていないがごとく実行してしまうもの があります。しかし単にそれらの文字をフィルタリングすると、いろいろな国の言葉 の使用を禁じてしまいます。 この場合はケースに応じて見ていく必要があります。

これに関連して、NIL キャラクタ(キャラクタの 0)が意外な影響を及ぼす問題が あげられます。 C や C++ の関数の大部分は、NIL キャラクタが文字列の終端の印と想定して いますが、他の言語(Perl や Ada95 等)の文字列を扱う関数は NIL を文字列の 一部として扱います。 ライブラリやカーネルの呼び出しは C の扱いを踏襲していますので、チェック する内容と実際使用される内容が一致しません[rfp 1999]。

他のプログラムを呼び出したり、ファイルを参照したりする時には、いつもフルパス (たとえば /usr/bin/sort)のように)で指定するようにして ください。 こうすることで、「間違った」コマンドを呼び出す際に生じるエラーを無くす だけでなく、PATH 環境変数が間違って設定されていてもエラーを回避できます。 他のファイルの参照についても、「間違った」開始パスを指定した結果生じる問題 を減らせます。