漏洞介绍

CMS Made Simple(CMSMS)是一个免费的开放源码内容管理系统,为开发人员、程序员和网站所有者提供基于网络的开发和管理功能。CVE-2019-9053是一个未验证的SQL注入漏洞,攻击者可利用该漏洞获取管理员密码或密码重置令牌。

漏洞范围

CMS Made Simple < 2.2.10

漏洞靶场

使用vulhub的靶场:

使用的环境是cmsms 2.2.9.1

vulhub-master/cmsms/CVE-2019-9053

启动靶场环境:

1
docker-compose up -d

服务启动后,访问http://your-ip/install.php并安装CMS服务。安装过程请根据页面中的安装向导来进行,其中MySQL数据库的地址是db,数据库名是cmsms,账号和密码均为root

image-20250316184033911

一路Next安装

访问http://127.0.0.1出现首页说明安装成功

image-20250316184207632

漏洞原理

漏洞文件为 :/modules/News/action.default.php
主要内容如下:

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
<?php
……
$entryarray = array();
$query1 = "
SELECT SQL_CALC_FOUND_ROWS
mn.*,
mnc.news_category_name,
mnc.long_name,
u.username,
u.first_name,
u.last_name
FROM " .CMS_DB_PREFIX . "module_news mn
LEFT OUTER JOIN " . CMS_DB_PREFIX . "module_news_categories mnc
ON mnc.news_category_id = mn.news_category_id
LEFT OUTER JOIN " . CMS_DB_PREFIX . "users u
ON u.user_id = mn.author_id
WHERE
status = 'published'
AND
";

if( isset($params['idlist']) ) {
$idlist = $params['idlist'];
if( is_string($idlist) ) {
$tmp = explode(',',$idlist);
for( $i = 0; $i < count($tmp); $i++ ) {
$tmp[$i] = (int)$tmp[$i];
if( $tmp[$i] < 1 ) unset($tmp[$i]);
}
$idlist = array_unique($tmp);
$query1 .= ' (mn.news_id IN ('.implode(',',$idlist).')) AND ';
}
}
……

if( isset($params['showall']) ) {
// show everything irrespective of end date.
$query1 .= 'IF(start_time IS NULL,news_date <= NOW(),start_time <= NOW())';
}
else {
// we're concerned about start time, end time, and news_date
if( isset($params['showarchive']) ) {
// show only expired entries.
$query1 .= 'IF(end_time IS NULL,0,end_time < NOw())';
}
else {
$query1 .= 'IF(start_time IS NULL AND end_time IS NULL,news_date <= NOW(),NOw() BETWEEN start_time AND end_time)';
}
}
……
$dbresult = $db->SelectLimit( $query1,$pagelimit,$startelement );
……
?>

/modules/News/action.default.php#L40-L70, L95-L108, L174

代码实例比较简单,params变量为重写的GET和POST请求,在这里idlist参数通过GET的方式获得传入的数值,经过字符判断,数组分割,再剔除无用数据,判断有无重复值,然后直接拼接到SQL语句query1中,最后在SelectLimit函数中执行。
SelectLimit函数如下:

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
public function &SelectLimit( $sql, $nrows = -1, $offset = -1, $inputarr = null )
{
$limit = null;
$nrows = (int) $nrows;
$offset = (int) $offset;
if( $nrows >= 0 || $offset >= 0 ) {
$offset = ($offset >= 0) ? $offset . "," : '';
$nrows = ($nrows >= 0) ? $nrows : '18446744073709551615';
$limit = ' LIMIT ' . $offset . ' ' . $nrows;
}

if ($inputarr && is_array($inputarr)) {
$sqlarr = explode('?',$sql);
if( !is_array(reset($inputarr)) ) $inputarr = array($inputarr);
foreach( $inputarr as $arr ) {
$sql = ''; $i = 0;
foreach( $arr as $v ) {
$sql .= $sqlarr[$i];
switch(gettype($v)){
case 'string':
$sql .= $this->qstr($v);
break;
case 'double':
$sql .= str_replace(',', '.', $v);
break;
case 'boolean':
$sql .= $v ? 1 : 0;
break;
default:
if ($v === null) $sql .= 'NULL';
else $sql .= $v;
}
$i += 1;
}
$sql .= $sqlarr[$i];
if ($i+1 != sizeof($sqlarr)) {
$false = null;
return $false;
}
}
}
$sql .= $limit;

$rs = $this->do_sql( $sql );
return $rs;
}

lib/classes/Database/class.Connection.php#L285-L330

可以看到,该函数将传入进来的SQL语句,拆分并装入数组,根据数组内字符类型不同而进行不同的处理,最后再重新拼接起来,执行SQL语句。这样的处理就导致了普通的SQL注入无法进行,如上文中的联合注入(联合注入中的一些字符在进行分割时被丢弃了)和普通布尔型注入(这里的SQL语句仅仅是整个SQL语句中的片段,并不影响整个SQL语句执行结果的TRUE或者FALSE)。
如下图,为整个SQL查询的语句:

4.png

漏洞复现

poc使用

使用https://www.exploit-db.com/exploits/46635的poc

1
python2 poc.py -u http://your-ip

image-20250316191247168

获取了用户名,邮箱,密码的盐值和hash值

根据源码可知,密码是盐值与密码进行了拼接后进行了md5处理

image-20250316191946008

参考

https://cloud.tencent.com/developer/article/1472573

https://blog.csdn.net/cn_lyxc/article/details/93865267

https://blog.csdn.net/shierbai/article/details/139952514