安全模式下exec等函数安全隐患 -电脑资料

电脑资料 时间:2019-01-01 我要投稿
【www.unjs.com - 电脑资料】

    当safe_mode=on且safe_mode_exec_dir为空时[默认为空],php在处理这一过程中存在安全隐患,在windows下exec()/system()/passthru()可以通过引入\来执行程序,绕过安全模式

    author: 80vul-B

    team:http://www.80vul.com

    date:2009-05-27

    一 前言

    就在昨天milw0rm上公布了一个非常有意思的漏洞:PHP <= 5.2.9 Local Safemod Bypass Exploit (win32).作者看到公告后,在php源代码基础上分析了下这个问题的根本原因,于是就有本文.

    二 描叙

    在php手册了有一节:<被安全模式限制或屏蔽的函数> 给出了受安全模式影响的一些函数如:

    backtick operator 本函数在安全模式下被禁用,

安全模式下exec等函数安全隐患

    shell_exec()(在功能上和 backticks 函数相同) 本函数在安全模式下被禁用。

    exec() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。

    system() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。

    passthru() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。

    popen() 只能在 safe_mode_exec_dir 设置的目录下进行执行操作。基于某些原因,目前不能在可执行对象的路径中使用 ..。escapeshellcmd() 将被作用于此函数的参数上。

    细心的朋友可能发现了在安全模式下,对于exec()/system()/passthru()/popen()这4个函数不是被禁用的,只是需要在safe_mode_exec_dir目录下执行,但当safe_mode=on且safe_mode_exec_dir为空时[默认为空],php在处理这一过程中存在安全隐患,在windows下exec()/system()/passthru()可以通过引入\来执行程序:)

    [ps:popen()不存在这个问题,文章后面会给出原因.]

    三 代码分析:

    以exec()函数为例分析下源码:

    PHP代码

    C++代码

    // 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代码

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

    ....

    四 测试代码

    PoC:

    PHP代码

   

    // safe_mode=On and safe_mode_exec_dir not set in php.ini

    // test on win32

    echo exec('\dir');

    // system('\dir');

    // passthru('\dir');

    ?>

    五 实际利用:

    PHP <= 5.2.9 SafeMod Bypass Vulnerability [by www.abysssec.com]

    http://www.milw0rm.com/exploits/8799

最新文章