2011-01-01 2011-01-01
●PHPのescapeshellcmdの危険性
本を書いています。初稿を一通り書き上げ、第2稿を作成中です。その過程で見つけたことを報告します。
PHPのescapeshellcmdはパラメータをクォートしないので呼び出し側でクォートする必要がありますが、escapeshellcmdの仕様がまずいために、呼び出し側でクォートしても突破できることが分かりました。
escapeshellcmdの仕様
PHPにはシェルのパラメータをエスケープする関数が2つあります。escapeshellargとescapeshellcmdです。escapeshellargは、エスケープだけでなくシングルクォートでクォートもしてくれます(引用符で囲むことをクォートするといいます)。エスケープ方法も、シングルクォートで囲む場合のエスケープ方法に従っているので、割合安心して利用できます*1。一方、escapeshellcmdは、エスケープのみでクォートしないので、呼び出し側でクォートする必要があります。
PHPのマニュアルには、このあたりのことがちゃんと書いてあります。
<?php
$e = escapeshellcmd($userinput);
// ここでは $e がスペースを含んでいても関係ない
system("echo $e");
$f = escapeshellcmd($filename);
// ここでは気を遣い、クォートを使用する
system("touch \"/tmp/$f\"; ls -l \"/tmp/$f\"");
?>
[PHP: escapeshellcmdより引用]
スペースのことを気にする理由は、スペースにより第2、第3の追加のパラメータを挿入される攻撃を防ぐためでしょう。その考え方自体は問題ありません。しかし、ここに穴はないでしょうか。
追加のパラメータの挿入を防ぐためには、ダブルクォートで囲むだけではダメで、パラメータ中にダブルクォートがある場合は、ダブルクォートをエスケープする必要があります。以下のように。
echo "abc\"def"
この結果は「abc"def」となります。ところが、escapeshellcmdはダブルクォートをエスケープしますが、マニュアルに妙なことが書いてあります。
' および " は、対になっていない場合にのみエスケープされます。
[PHP: escapeshellcmdより引用]
ということは、対になっている場合はエスケープしないのでしょうか。さっそく試してみましょう。
【サンプル】
<?php
echo escapeshellcmd('abc"def"ghi') . "\n";
【実行結果】
abc"def"ghi
マニュアルに書いてある通りですね。ダブルクォートが3個以上の場合は、偶数の場合はエスケープなし、奇数の場合は最後のダブルクォートのみエスケープされるようです。しかし、これではまずい場合があります。PHPのマニュアルに書いてある「気を遣」っている方のサンプルに、「aaa" "/etc/passwd」を入力してみましょう。
【スクリプト】
$filename = 'aaa" "/etc/passwd';
$f = escapeshellcmd($filename);
system("touch \"/tmp/$f\"; ls -l \"/tmp/$f\"");
【実行結果】
touch: `/etc/passwd'にtouchできませんでした: Permission denied
-rw-r--r-- 1 root root 1320 2010-12-31 14:48 /etc/passwd
-rw-r--r-- 1 wasbook wasbook 0 2011-01-01 17:33 /tmp/aaa
第2のパラメータとして、/etc/passwdが挿入されています。touchの方は権限がないのでエラーになっていますが、lsの方はしっかり表示されています。この際のsystem関数の引数は以下の通りです。
touch "/tmp/aaa" "/etc/passwd"; ls -l "/tmp/aaa" "/etc/passwd"
コマンドラインという観点からは非の打ち所がない指定ですが、セキュリティという観点では非常に問題です。これにより、意図しないファイルを追加されたり、オプションを追加する(例えば、findコマンドの-execオプション)ことで、意図しない動作を引き起こす可能性があります。
この原因は、escapeshellcmdの「' および " は、対になっていない場合にのみエスケープされます」という仕様にあります。まったく余計なお世話としか言いようがありません。この仕様では、恐ろしくてescapeshellcmdは使えませんし、マニュアルにここまではっきり書いてある仕様を今さら変えられないでしょう。escapeshellcmdはお蔵入りするしかないと思います。幸い、escapeshellargの方はまともな仕様と思われますので、escapeshellargで代替してください。
ただし、そもそもOSコマンドを呼ぶのがよいかとか、もっと良い方法はないのかという疑問が出てきます。もっと良い方法はあります。それは、本が出てからのお楽しみ、ということで。
追記:2011年1月4日 12:50
escapeshellcmdを使うと危険となる実例について、日記を書きましたので参考になさってください。。escapeshellcmdの危険な実例
*1 ただし、ロケールの問題は注意が必要です