CMS Made Simple Blind SQL Injection, Resurrected

This was found while working on a HackTheBox challenge. I won’t go into details of the challenge, because that’s not the point.

The challenge box was running an old version of CMS Made Simple, a Content Management System written in PHP.

When approaching the box, it’s easy to determine the version and find the public exploit for CVE-2019-9053 and cheese it. However in my opinion, that’s not the point. So instead, I went out and fetched a copy of the source code for CMS Made Simple.

I spent a while getting to grips with the source and how it was written and one thing immediately stuck out, there was a read file function which was reachable without any authentication. The code for these modules was normally prefixed with a section of code that checks the authentication before proceeding, and it was obviously missing.

As such, we could construct a query string...

?mact=FileManager,m1_,view,0&m1_file=Li4vY29uZmlnLnBocA==

Where Li4vY29uZmlnLnBocA== is ../config.php encoded with base64, and the service would return the file content to us providing an unauthenticated arbitrary file read.

It turned out that this was CVE-2018-10522 and was a known vulnerability that had never been patched. The vendor was not actually aware of it, which doesn’t surprise me as reporting the bug was not easy and they actively resisted attempts to contact them about it.

However, the report for CVE-2018-10522 seemed to miss the fact that this was pre-auth making this much easier to exploit.

Unfortunately, a file read wasn’t sufficient to claim the flag for the challenge box so I had to dig deeper.

This is where CVE-2019-9053 comes in, it’s a blind SQL injection vulnerability in the News module. The code vulnerable to CVE-2019-9053 is as follows…

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 ';
    }
}

The original exploit for this was found and created by Daniele Scanu and can be found here.

The patched code looks like this…

if( isset($params['idlist']) ) {
    $idlist = $params['idlist'];
    if( is_string($idlist) ) {
        $tmp = explode(',',$idlist);
        $idlist = [];
        for( $i = 0; $i < count($tmp); $i++ ) {
            $val = (int)$tmp[$i];
            if( $val > 0 && !in_array($val,$idlist) ) $idlist[] = $val;
        }
    }
    if( !empty($idlist) ) $query1 .= ' (mn.news_id IN ('.implode(',',$idlist).')) AND ';
}

It checks to see if it’s a string, and if so it splits it around the comma and converts them to ints, then implodes it. The problem is that the code seems to assume that the input must be a number or a string. An obvious solution presents itself: Send an array.

This allows us to once again smuggle arbitrary content into the SQL query string ($query1) and perform SQL injection.

This specific exploit was patched in CMSMS 2.2.13.

You can review the tool here.