はじめに
私は6年前に、PHP Advent Calendar 2013として「PHPだってシェル経由でないコマンド呼び出し機能が欲しい」という記事を書きました。その中で、OSコマンドインジェクション対策の根本的かつ安全な対策は「シェルを経由しないコマンド呼び出し」であることを指摘した上で、末尾に以下のように書きました。PHPコミッタのみなさま、PHP5.6の新機能として、シェルを経由しないコマンド呼び出しの機能を追加できませんか?現実には当時からPCNTL関数にてシェルを経由しないコマンド呼び出しはできたのですが、当関数の使用が難しいことと、CLI版あるいはCGI版(FastCGIは可)のPHPでないとサポートされていないなどの制限があり、popenやproc_openなど使いやすいコマンド呼び出し関数において、シェル呼び出しのないコマンド実行機能が欲しいところでした。
この「私の願い」はPHP 5.6では実現しませんでしたが、PHP 7.4においてproc_open関数の拡張として実現しました。実に6年越しの実現ということになります。
proc_openの従来の問題点
proc_openに限りませんが、PHPの従来のコマンド実行機能(PCNTL関数は例外)の問題として、「常にシェル経由でコマンドを呼び出す」ことがあります。これを確認するための簡単なサンプルを示します。以下は、ps -fコマンドをproc_open関数で呼び出しています。呼び出し例は下記となります。赤字で示しているように、シェル(/bin/sh)経由でpsコマンドが実行されています。<?php $cmd = "ps -f"; $process = proc_open($cmd, [], $pipes); if (is_resource($process)) { $return_value = proc_close($process); echo "command returned $return_value\n"; }
UID PID PPID C STIME TTY TIME CMD
ockeghem 16921 16920 0 16:31 pts/0 00:00:00 -bash
ockeghem 18858 16921 0 17:18 pts/0 00:00:00 php-7.4.0 proc_open4.php
ockeghem 18859 18858 0 17:18 pts/0 00:00:00 sh -c ps -f
ockeghem 18860 18859 0 17:18 pts/0 00:00:00 ps -f
command returned 0
このため、コマンドラインにセミコロンなどにより追加のコマンドを実行できる可能性があり、OSコマンドインジェクション脆弱性の原因になっていました。ここで、psのオプションとして、-fの代わりに、「-f; echo Hello」を指定してみましょう。呼び出し例は下記となります。sh -c のパラメータとして; echo Helloが追加されていることと、echoコマンドの実行結果としてHelloが表示されていることがわかります。これがOSコマンドインジェクションの原理です。<?php $cmd = "ps -f; echo Hello"; $process = proc_open($cmd, [], $pipes); // 以下省略
この対策として、コマンドラインのパラメータをエスケープ処理する方法もありますが、エスケープ処理自体が複雑になる可能性があり、実際にPHPのescapeshellcmd関数には脆弱性(こちらを参照)があるため使用を避けるべき状態でした。UID PID PPID C STIME TTY TIME CMD ockeghem 16921 16920 0 16:31 pts/0 00:00:00 -bash ockeghem 18932 16921 0 17:30 pts/0 00:00:00 php-7.4.0 proc_open4.php ockeghem 18933 18932 0 17:30 pts/0 00:00:00 sh -c ps -f; echo Hello ockeghem 18934 18933 0 17:30 pts/0 00:00:00 ps -f Hello command returned 0
proc_openのPHP 7.4での新しい呼び出し方
この状況に対して、PHP 7.4では、proc_openの第1引数を配列として指定することにより、コマンドとパラメータを明確に分離するとともに、シェルを経由しないコマンド実行ができるようになりました(パチパチパチ)。先のスクリプトをこの形式で書き換えてみましょう。
<?php
$cmd = ["ps", "-f"];
$process = proc_open($cmd, [], $pipes);
if (is_resource($process)) {
$return_value = proc_close($process);
echo "command returned $return_value\n";
}
実行例は以下となります。シェルを経由せずに直接コマンドが実行されていることがわかります。続いて、先程同様に、-f オプションの代わりに -f; echo Hello を指定してみましょう。UID PID PPID C STIME TTY TIME CMD ockeghem 16921 16920 0 16:31 pts/0 00:00:00 -bash ockeghem 18895 16921 0 17:27 pts/0 00:00:00 php-7.4.0 proc_open4.php ockeghem 18896 18895 0 17:27 pts/0 00:00:00 ps -f command returned 0
<?php
$cmd = ["ps", "-f; echo Hello"];
$process = proc_open($cmd, [], $pipes);
// 以下略
実行結果は以下のとおりです。psコマンドにオプションとして「-f; echo Hello」が渡されたため、「unsupported SysV option」というエラーになっていますが、OSコマンドインジェクションにはならないことがわかります。この呼出方法(proc_openの第一引数を配列で指定)の場合、シェルを経由しないでコマンドを呼び出すことから、原理的にOSコマンドインジェクション脆弱性を避けることができます。今後PHP 7.4以降にて外部コマンドを呼び出す場合は常に、proc_open関数にて第1引数を配列で指定し、かつ配列の先頭要素(コマンド名)は固定とすることで、OSコマンドインジェクションを避けつつ簡便かつ安全な実装が可能になります。error: unsupported SysV option Usage: ps [options] Try 'ps --help <simple all="" list="" misc="" output="" threads="">' or 'ps --help <s a="" l="" m="" o="" t="">' for additional help text. For more details see ps(1). command returned 1
0 件のコメント:
コメントを投稿