前回:やられアプリ BadTodo - 8.1 OS コマンド・インジェクション(リモートコード実行。CVE-2012-1823) - demandosignoで紹介したように、PHPの exec(), system() などの関数はOSコマンドを呼び出すことができます。多くはシェル経由でコマンドを起動しています。
----- 以下「安全なWebアプリケーションの作り方」p296~297より一部引用 -----
system("echo hello > a.txt");
PHPでのsystem関数の呼び出し
↓
/bin/sh -c echo hello > a.txt
実際に起動されているコマンド。/bin/sh 経由で呼び出されている。
シェル経由でコマンドを起動することにより、パイプ機能やリダイレクト機能などを利用しやすくなりますが、ここにOSコマンドインジェクションの原因があります。
シェルによる複数コマンド実行例。
$ echo aaa ; echo bbb # コマンドを続けて実行
aaa
bbb
$ echo ls | grep home # 第1のコマンドの出力を第2のコマンドの入力に
home
このように複数のコマンドを起動するための構文があるため、外部からパラメータを操作することにより元のコマンドに加えて別のコマンドを起動させられる場合があります。
----- 引用ここまで -----
proc_open 関数
今回は、BadTodoの「問い合わせ」画面を例に出します。以下のようになっており、項目を入力し「送信」すると問い合わせが送られます。
ここでEメール欄にtest@example.com;cat /etc/passwd
と入力して送信してみます。
「メールアドレスの形式が不正です」となりました。バリデーションチェックが入っています。ソースコード common.php は下記です。
// RFC準拠のメールアドレス検証 function validEmailAddress($email) { return preg_match( '/\A(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})~(中略)~(?>\.(?9)){3}))\])(?1)\z/isD', $email ); }
メールアドレスのローカルパートとドメイン部の構造を厳密にチェックしています。今回はOSコマンドインジェクションを試すため、このバリデーションチェックを外して試しました。
return preg_match( /*'/\A(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})~(中略)~*/ //←コメントアウト '/(.*)/', //←追記 $email );
再送信します。バリデーションチェックがかからず問い合わせが受け付けられました。
しかし何も起こりません。受信メールを見ると /etc/passwd はアドレスの一つとして認識されているようです。
ソースコードは次の通り(inquerydo.php)
$process = proc_open("/usr/sbin/sendmail -i \"$email\"", $descriptorspec, $pipes);
$email の部分に入力された文字列test@example.com;cat /etc/passwd
が入ると
$process = proc_open("/usr/sbin/sendmail -i \"test@example.com;cat /etc/passwd\"", $descriptorspec, $pipes);
となりますが、前後がエスケープされた "" で囲まれているためコマンドではなくただの文字列として認識されています。
では入力文字列に " を加え test@example.com";cat /etc/passwd" とするとどうでしょう。
/etc/passwd が表示されました。先ほどとは違いメールアドレスとしては認識されていません。
入力が test@example.com";cat /etc/passwd" ですので次のようになり、
$process = proc_open("/usr/sbin/sendmail -i \"test@example.com";cat /etc/passwd"\"", $descriptorspec, $pipes);
前半の " を " で閉じ、後半の " も " で閉じているため ;cat /etc/passwd がコマンドとして認識されます。
対策
OSコマンドインジェクションの対策として
・OSコマンド呼び出しを使わない実装方法を選択する → sendmail ではなく PHPのライブラリ mb_sendmail() を使うなど
・外部から入力された文字列をコマンドラインのパラメータに渡さない
・OSコマンドに渡すパラメータを安全な関数によりエスケープする
保険的対策として
・パラメータの検証 → 今回行われていたメールアドレスのバリデーションチェック
などがあります。
次回:やられアプリ BadTodo - 9 Server Side Code Injection - PHP Code Injection - demandosigno