首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>漏洞检测>文章内容
mb_ereg(i)_replace()代码注射漏洞及其延伸出的正则应用安全问题
来源:http://www.80vul.com 作者:ryat 发布时间:2009-05-08  
mb_ereg(i)_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部分不被正则匹配,将会作为返回值[或者返回值的一部分]返回:) 五 实际应用 暂缺
 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·MS07-014调试手记
·udp_sendmsg空指针漏洞分析
·一段对perl 木马的分析
·小心我“DIR”溢出你!
·CVE-2010-4258漏洞分析
·Adobe Reader 'CoolType.dll' TT
·PHPBB<+2.0.10 漏洞说明
·袁哥写的漏洞研究方法总结
·利用异常处理执行shellcode实例
·注入点检测新方法
·Apache mod_ssl buffer over分析
·RPC漏洞的通用分析方法
  相关文章
·preg_match(_all)的变量初始化问
·intval()使用不当导致安全漏洞的
·读新术-基于开源代码更新的漏洞
·采众家之长分析及改进Cmail漏洞
·udp_sendmsg空指针漏洞分析
·一个CGI程序的漏洞挖掘
·RPC漏洞的通用分析方法
·对一款国家级内容过滤系统Dos安
·MS07-014调试手记
·小心我“DIR”溢出你!
·Symantec 核心驱动 symtdi.sys
·Rav 核心驱动 memscan.sys 本地
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved