徳丸さんのブログでescapeshellcmdの余計なお世話の件が指摘されていたのでパッチを作りました。これmagic quoteと同じレベルの余計なお世話なのですが放置されています。個人的にはどのような関数にも全てバリデーション済みの文字列しか渡さないのでセキュリティ問題は発生しないのですが、UNIX系OSではペアとなる”と’はエスケープしない仕様に気が付いていないプログラマも多いかもしれません。
先ほど「UNIX系OSでは」と書きましたがWindowsでは異なる動作をします。この関数は仕様がいい加減でWindowsの場合は”と’も問答無用でエスケープします。(加えて%もエスケープします)ですから、徳丸さんが指摘された問題はWindowsを使っていれば発生しません。UNIX系とWindowsで動作が異なる、という厄介な関数でもあります。(セキュリティ上の問題は認識されていて、何故かWindow系のサポートは後で追加されたのでまともな動作になっています。イラっとしてしまう仕様です)
取り敢えず、UNIX系OSでもescapeshellcmd関数にオプションを渡すことによりエスケープの動作を調整できるようなパッチを書きました。
一応セキュリティチームに投げておきますが、修正されるとしてもPHP 5.3だけでしょう。PHP 5.4以降になる可能性も高いです。どうしても今必要な方は自分でパッチを組み込んで使ってください。
追記:何も考えず30分ほどで作ったので定数名が変ですね。。。もしリリース版に取り込むとしても定数名は変更される可能性が高いのでこのままにしておきます。
使い方
- escapeshellcmd($str, ESCAPE_SHELL_CMD_ALL); // 全部エスケープ
- escapeshellcmd($str, ESCAPE_SHELL_CMD_END); // 先頭と終端以外はエスケープ
- escapeshellcmd($str, ESCAPE_SHELL_CMD_PAIR); // ペア以外はエスケープ(デフォルト)
第二引数のフラグを渡さないとデフォルトの動作をします。Windows系の動作は触っていないのでフラグを渡しても渡さなくても問答無用でエスケープします。(ESCAPE_SHELL_CMD_ALLと同じ)
パッチ
diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c
index f7c82dd..5f269bf 100644
--- a/ext/standard/basic_functions.c
+++ b/ext/standard/basic_functions.c
@@ -3609,6 +3609,7 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */
 REGISTER_INI_ENTRIES();
 register_phpinfo_constants(INIT_FUNC_ARGS_PASSTHRU);
+    register_exec_constants(INIT_FUNC_ARGS_PASSTHRU);
 register_html_constants(INIT_FUNC_ARGS_PASSTHRU);
 register_string_constants(INIT_FUNC_ARGS_PASSTHRU);
diff --git a/ext/standard/exec.c b/ext/standard/exec.c
index 713a8a0..a538390 100644
--- a/ext/standard/exec.c
+++ b/ext/standard/exec.c
@@ -51,6 +51,17 @@
 #include <unistd.h>
 #endif
+/* {{{ register_exec_constants
+ *  */
+void register_exec_constants(INIT_FUNC_ARGS)
+{
+    REGISTER_LONG_CONSTANT("ESCAPE_SHELL_CMD_PAIR", ESCAPE_SHELL_CMD_PAIR, CONST_PERSISTENT|CONST_CS);
+    REGISTER_LONG_CONSTANT("ESCAPE_SHELL_CMD_END", ESCAPE_SHELL_CMD_END, CONST_PERSISTENT|CONST_CS);
+    REGISTER_LONG_CONSTANT("ESCAPE_SHELL_CMD_ALL", ESCAPE_SHELL_CMD_ALL, CONST_PERSISTENT|CONST_CS);
+}
+/* }}} */
+
+
 /* {{{ php_exec
 * If type==0, only last line of output is returned (exec)
 * If type==1, all lines will be printed and last lined returned (system)
@@ -276,7 +287,7 @@ PHP_FUNCTION(passthru)
 *NOT* safe for binary strings
 */
-PHPAPI char *php_escape_shell_cmd(char *str)
+PHPAPI char *php_escape_shell_cmd_ex(char *str, int flag)
 {
 register int x, y, l = strlen(str);
 char *cmd;
@@ -304,14 +315,26 @@ PHPAPI char *php_escape_shell_cmd(char *str)
 #ifndef PHP_WIN32
 case '"':
 case '\'':
-                if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) {
-                    /* noop */
-                } else if (p && *p == str[x]) {
-                    p = NULL;
-                } else {
+                if (flag == ESCAPE_SHELL_CMD_ALL) {
 cmd[y++] = '\\';
+                    cmd[y++] = str[x];
+                } else if (flag == ESCAPE_SHELL_CMD_END) {
+                    if (x == 0 || x == l - 1) {
+                        cmd[y++] = str[x];
+                    } else {
+                        cmd[y++] = '\\';
+                        cmd[y++] = str[x];
+                    }
+                } else { /* ESCPAE_SHELL_CMD_PAIR */
+                    if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) {
+                        /* noop */
+                    } else if (p && *p == str[x]) {
+                        p = NULL;
+                    } else {
+                        cmd[y++] = '\\';
+                    }
+                    cmd[y++] = str[x];
 }
-                cmd[y++] = str[x];
 break;
 #else
 /* % is Windows specific for enviromental variables, ^%PATH% will 
@@ -363,6 +386,10 @@ PHPAPI char *php_escape_shell_cmd(char *str)
 return cmd;
 }
+PHPAPI char *php_escape_shell_cmd(char *str)
+{
+    return php_escape_shell_cmd_ex(str, ESCAPE_SHELL_CMD_PAIR);
+}
 /* }}} */
 /* {{{ php_escape_shell_arg
@@ -435,14 +462,15 @@ PHP_FUNCTION(escapeshellcmd)
 {
 char *command;
 int command_len;
+       long flag = ESCAPE_SHELL_CMD_PAIR;
 char *cmd = NULL;
-    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &command, &command_len) == FAILURE) {
+    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &command, &command_len, &flag) == FAILURE) {
 return;
 }
 if (command_len) {
-        cmd = php_escape_shell_cmd(command);
+        cmd = php_escape_shell_cmd_ex(command, flag);
 RETVAL_STRING(cmd, 0);
 } else {
 RETVAL_EMPTY_STRING();
diff --git a/ext/standard/exec.h b/ext/standard/exec.h
index 18ba008..b39a90e 100644
--- a/ext/standard/exec.h
+++ b/ext/standard/exec.h
@@ -21,6 +21,10 @@
 #ifndef EXEC_H
 #define EXEC_H
+#define ESCAPE_SHELL_CMD_PAIR  0 
+#define ESCAPE_SHELL_CMD_END   1 
+#define ESCAPE_SHELL_CMD_ALL   2
+
 PHP_FUNCTION(system);
 PHP_FUNCTION(exec);
 PHP_FUNCTION(escapeshellcmd);