BUGTRAQ ID: 35435 PHP是广泛使用的通用目的脚本语言,特别适合于Web开发,可嵌入到HTML中。 在安全模式下,PHP没有禁用exec()、system()、passthru()和popen()这四个函数,只是在 safe_mode_exec_dir目录下执行。但当safe_mode=on且safe_mode_exec_dir为空时(默认),PHP在处理这一过程中存在安全隐患,在windows下exec()/system()/passthru()可以通过引入“\”来执行程序。 以exec()函数为例分析源码: // exec.c PHP_FUNCTION(exec) { php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } // system(),passthru()函数也是调用的php_exec_ex但popen()不是 ... static void php_exec_ex(INTERNAL_FUNCTION_PARAMETERS, int mode) { char *cmd; int cmd_len; zval *ret_code=NULL, *ret_array=NULL; int ret; ... if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z/z/", &cmd, &cmd_len, &ret_array, &ret_code) == FAILURE) { RETURN_FALSE; } ... if (!ret_array) { ret = php_exec(mode, cmd, NULL, return_value TSRMLS_CC); ... int php_exec(int type, char *cmd, zval *array, zval *return_value TSRMLS_DC) { ... if (PG(safe_mode)) { if ((c = strchr(cmd, ' '))) { *c = '\0'; c++; } // 取cmd中的参数部分 if (strstr(cmd, "..")) {...
BUGTRAQ ID: 35435 PHP是广泛使用的通用目的脚本语言,特别适合于Web开发,可嵌入到HTML中。 在安全模式下,PHP没有禁用exec()、system()、passthru()和popen()这四个函数,只是在 safe_mode_exec_dir目录下执行。但当safe_mode=on且safe_mode_exec_dir为空时(默认),PHP在处理这一过程中存在安全隐患,在windows下exec()/system()/passthru()可以通过引入“\”来执行程序。 以exec()函数为例分析源码: // exec.c PHP_FUNCTION(exec) { php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } // system(),passthru()函数也是调用的php_exec_ex但popen()不是 ... static void php_exec_ex(INTERNAL_FUNCTION_PARAMETERS, int mode) { char *cmd; int cmd_len; zval *ret_code=NULL, *ret_array=NULL; int ret; ... if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z/z/", &cmd, &cmd_len, &ret_array, &ret_code) == FAILURE) { RETURN_FALSE; } ... if (!ret_array) { ret = php_exec(mode, cmd, NULL, return_value TSRMLS_CC); ... int php_exec(int type, char *cmd, zval *array, zval *return_value TSRMLS_DC) { ... if (PG(safe_mode)) { if ((c = strchr(cmd, ' '))) { *c = '\0'; c++; } // 取cmd中的参数部分 if (strstr(cmd, "..")) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "No '..' components allowed in path"); goto err; } // 不允许使用..来跳转目录 ,这个也是php手册描叙不让..的处理代码 b = strrchr(cmd, PHP_DIR_SEPARATOR); // 在win下PHP_DIR_SEPARATOR为\,*nix下为/,具体定义在main/php.h // 如果cmd是80vul\b\dir,那么这部分取得的值是\dir spprintf(&d, 0, "%s%s%s%s%s", PG(safe_mode_exec_dir), (b ? "" : "/"), (b ? b : cmd), (c ? " " : ""), (c ? c : "")); // 这句是这个安全隐患的关键处 // 如果php.ini中没有设置safe_mode_exec_dir的话,80vul\dir经过上面的处理为\dir[如果直接提交dir则会被处理为/dir] // 这个也是需要"safe_mode_exec_dir为空时[默认为空]"的原因 if (c) { *(c - 1) = ' '; } cmd_p = php_escape_shell_cmd(d); // 这里调用了php_escape_shell_cmd处理 ... #ifdef PHP_WIN32 fp = VCWD_POPEN(cmd_p, "rb"); #else fp = VCWD_POPEN(cmd_p, "r"); #endif ... char *php_escape_shell_cmd(char *str) { register int x, y, l; char *cmd; char *p = NULL; TSRMLS_FETCH(); l = strlen(str); cmd = safe_emalloc(2, l, 1); for (x = 0, y = 0; x < l; x++) { // 这里用的strlen,所以这个函数是not safe binary int mb_len = php_mblen(str + x, (l - x)); /* skip non-valid multibyte characters */ if (mb_len < 0) { continue; } else if (mb_len > 1) { memcpy(cmd + y, str + x, mb_len); y += mb_len; x += mb_len - 1; continue; } // 这部分代码是为了补se牛提出的那个编码问题:p // http://www.sektioneins.de/advisories/SE-2008-03.txt switch (str[x]) { ... case '\\': ... #ifdef PHP_WIN32 /* since Windows does not allow us to escape these chars, just remove them */ case '%': cmd[y++] = ' '; break; // 如果是win下的话,就把\等特殊字符去掉 // 那么\dir经过此函数处理后就变成dir了:) #endif cmd[y++] = '\\'; /* fall-through */ default: cmd[y++] = str[x]; ... // tsrm_win32.c TSRM_API FILE *popen_ex(const char *command, const char *type, const char *cwd, char *env) { ... cmd = (char*)malloc(strlen(command)+strlen(TWG(comspec))+sizeof(" /c ")); sprintf(cmd, "%s /c %s", TWG(comspec), command); if (!CreateProcess(NULL, cmd, &security, &security, security.bInheritHandle, NORMAL_PRIORITY_CLASS|CREATE_NO_WINDOW, env, cwd, &startup, &process)) { // 调用CreateProcess创建线程,执行命令 return NULL; } 对于popen()函数: PHP_FUNCTION(popen) { .... if (PG(safe_mode)){ b = strchr(Z_STRVAL_PP(arg1), ' '); if (!b) { b = strrchr(Z_STRVAL_PP(arg1), '/'); \\直接使用的“/”,根本没有考虑windows系统下对“\”的支持,所以也就不存在上面的问题 } else { char *c; c = Z_STRVAL_PP(arg1); while((*b != '/') && (b != c)) { b--; } if (b == c) { b = NULL; } } if (b) { spprintf(&buf, 0, "%s%s", PG(safe_mode_exec_dir), b); } else { spprintf(&buf, 0, "%s/%s", PG(safe_mode_exec_dir), Z_STRVAL_PP(arg1)); } tmp = php_escape_shell_cmd(buf); fp = VCWD_POPEN(tmp, p); .... PHP <= 5.2.10 厂商补丁: PHP --- 目前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载: <a href="http://bugs.php.net/bug.php?id=45997" target="_blank" rel=external nofollow>http://bugs.php.net/bug.php?id=45997</a>