Attacker Value
Unknown
(1 user assessed)
Exploitability
Unknown
(1 user assessed)
User Interaction
Unknown
Privileges Required
Unknown
Attack Vector
Unknown
0

CMS Made Simple (CMSMS) Showtime2 Post Auth Arbitrary File Upload Vulnerability

Disclosure Date: March 11, 2019 Last updated February 13, 2020
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

CMSMS’s Showtime2 module is vulnerable to an arbitrary file upload vulnerability. An authenticated attacker can exploit this by uploading a malicious payload, and gain remote code execution.

Add Assessment

1
Technical Analysis

Background

CMS Made Simple (CMSMS) is an open source content management system. It can be used for various purposes such as galleries, company and user directories, guestbooks, E-Commerce, blogs, etc, depending on the module the user installs. It is written in PHP, and runs on MySQL.

One of the commonly downloaded modules for CMSMS is called Showtime2, a slideshow feature. In it, the watermark support allows an authenticated user (likely an administrator) to upload a watermark image, which can be abused to upload a malicious payload.

A Metasploit module was submitted on March 19th 2019, which allowed me to investigate the vulnerability.

Vulnerability Analysis

Environment Setup

In order to analyize the vulnerability, we need to set up a vulnerable environment. The minimal requirements are:

  • A Ubuntu VM that supports Apache, PHP, and MySQL.
  • CMS Made Simple. Since the vulnerability doesn’t actually come from the CMS, the latest should work.
  • A vulnerable version of Showtime2. You can just download the XML file, and import it from the module manager in CMSMS. Once imported, an “install” button will be available for you to actually install the vulnerable Showtime2 module.

Debugging CMSMS

Like other exploit analysis cases, we usually start off with a proof-of-concept from the Metasploit module, and this one is no exception. Since the vulnerability involves uploading something over HTTP, the key moment would be this block of code from the exploit:

data = Rex::MIME::Message.new
data.add_part('Showtime2,m1_,defaultadmin,0', nil, nil, "form-data; name=\"mact\"")
data.add_part('Upload', nil, nil, "form-data; name=\"m1_upload_submit\"")
data.add_part(@csrf_value, nil, nil, "form-data; name=\"#{@csrf_name}\"")
data.add_part(fcontent, 'text/plain', nil, "from-data; name=\"m1_input_browse\"; filename=\"#{fname}\"")

res = send_request_cgi(
  'method' => 'POST',
  'uri' => normalize_uri(target_uri, 'admin', 'moduleinterface.php'),
  'ctype' => "multipart/form-data; boundary=#{data.bound}",
  'data' => data.to_s,
  'headers' => {
    'Cookie' => @cookies
    }
  )

We can see that the exploit uses the Metasploit’s HttpClient API to make a request to admin/moduleinterface.php, so this would be our starting point for the analysis. What is the application doing to our malicious upload request? Let’s check it out.

By looking at the source code for moduleinterface.php, my high level understanding of the code is that this is meant for loading a third-party module. One of the first things we see is the use of the mact parameter, and how the code wants to split that into multiple variables:

if (isset($_REQUEST['mact'])) {
    $ary = explode(',', cms_htmlentities($_REQUEST['mact']), 4);
    $module = (isset($ary[0])?$ary[0]:'');
    $id = (isset($ary[1])?$ary[1]:'m1_');
    $action = (isset($ary[2])?$ary[2]:'');
}

Based on the mact data coming from the exploit, we can break down our data to these:

  • module = “Showtime2”
  • id = “m1_”
  • action = “defaultadmin”

After that, moduleinterface.php creates a new instance for the module the exploit asked by doing:

$modinst = ModuleOperations::get_instance()->get_module_instance($module);

We know that the exploit is requesting the Showtime2 module, which means $modinst is technically a Showtime2 object (found as Showtime2.module.php), and it extends CMSModule (the base class that can be found as class.CMSModule.php). Once the instance is ready, the module interface triggers an action toward the end of the code:

$modinst->DoActionBase($action, $id, $params, '', $smarty);

For a CMSMS module, the term “action” means the a feature to support. For example, if you have a module that supports editing a slideshow, then you could call your action “editslideshow”, and this would be implemented as its own PHP file.

The DoAction* methods actually come from CMSModule (the base class the Showtime2 object extends from), and it basically triggers a PHP file to be included from the modules directory (oh and look, there is even a directory traversal patch):

if ($name != '') {
  //Just in case DoAction is called directly and it's not overridden.
  //See: http://0x6a616d6573.blogspot.com/2010/02/cms-made-simple-166-file-inclusion.html
  $name = preg_replace('/[^A-Za-z0-9\-_+]/', '', $name);

  $filename = $this->GetModulePath().'/action.' . $name . '.php';
  if( !is_file($filename) ) {
    @trigger_error("$name is an unknown acton of module ".$this->GetName());
    throw new \CmsError404Exception("Module action not found");
  }

  // these are included in scope in the included file for convenience.
  $gCms = CmsApp::get_instance();
  $db = $gCms->GetDb();
  $config = $gCms->GetConfig();
  $smarty = ( $this->_action_tpl ) ? $this->_action_tpl : $smarty = $gCms->GetSmarty()->get_template_parent();
  include($filename);
}

If you recall what the exploit is sending in the mact parameter, the specific action we should be looking for is defaultadmin. So based on the above code, we should be looking at action.defaultadmin.php in Showtime2’s directory.

And that’s about how much we need to know about the mechanics of CMSMS, let’s move on to the Showtime2 code.

Debugging Showtime2’s defaultadmin Action

The most interesting part of the defaultadmin action code is of course the upload routine, which occurs almost at the beginning of the file:

if( isset($params['upload_submit'])){
  $params = array('active_tab' => 'watermark');

  $fieldName=$id.'input_browse';

  if (!isset ($_FILES[$fieldName]) || !isset ($_FILES)
    || !is_array ($_FILES[$fieldName]) || !$_FILES[$fieldName]['name']){
    $params['message'] = $this->Lang('error_nofilesuploaded');
    $smarty->assign('message',$this->Lang('error_nofilesuploaded'));
  }else{
    $file = $_FILES[$fieldName];
    // cleanup the filename
    $pos = strrpos($file['name'], '.');
    $alias = substr($file['name'], 0, $pos);
    $alias = preg_replace('/[^a-z0-9-_]+/i','-',$alias);
    $alias = trim($alias . substr($file['name'], $pos), '-');
    $uploadfile = $config['image_uploads_path'].'/'. $alias;

    if (!@move_uploaded_file($file['tmp_name'], $uploadfile)) {
      $smarty->assign('message',$this->Lang('error_nofilesuploaded'));
    }else{
      chmod($uploadfile, 0644);
      $this->SetPreference("watermark_file", $alias);
      $smarty->assign('message',$this->Lang('file_uploaded'));
      $create_watermark =true;
    }
  }
}

After the file is uploaded, it is treated as an image. For example, the next step after the upload is watermarking the image:

if  ($create_watermark){
  $source_image = '../modules/Showtime2/images/watermark_example_org.jpg';
  $dest_image = '../modules/Showtime2/images/watermark_example_new.jpg';
  if(!showtime2_image::watermark_image($source_image,$dest_image,false)){
    $smarty->assign('message',$this->Lang('watermark_warning'));
  }
}

But really, there is way to be sure whether the user uploaded is an image or not; the code just assumes it is. This is what allows the attacker to upload whatever they want and leave a backdoor on the target server.

And this is how much we need to know about the vulnerability. Now that we know how CMSMS utilizes module interface to load a module, and how our file is uploaded, let’s look at the patch.

Patch Analysis

According to the diff here, we know that the patch is implemented in the watermark_image function in class.showtime2_image.php. And it went from the vulnerable code like this (from rev 14)

$watermark_file = $mod->GetPreference('watermark_file');
if ($watermark_file=='watermark.png'){
  $watermark_file = $config['root_path'].'/modules/Showtime2/images/watermark.png';
}else{
  $watermark_file = $config['image_uploads_path'].'/'.$watermark_file;
}
if (!file_exists($watermark_file)) return false;

To this (rev 47, with the log message indicating this is a security fix):

$watermark_file = $mod->GetPreference('watermark_file');
if ($watermark_file=='watermark.png'){
  $watermark_file = $config['root_path'].'/modules/Showtime2/images/watermark.png';
}else{
  $watermark_file = $config['image_uploads_path'].'/'.$watermark_file;
}
$fext = strtoupper(substr($watermark_file, strrpos($watermark_file, '.')));
if (!in_array($fext,array('.GIF','.JPG','.JPEG','.PNG')))
  unlink($watermark_file);
if (!file_exists($watermark_file)) return false;

So it looks like the intention of the fix is to check the file extension and make sure the file type is one of these: GIF, JPG, JPEG, and PNG. If there is no match, then it deletes the uploaded file. For the most part, that sounds like a good plan.

A slight concern is that in PHP, you don’t always need the file to be .php to be able to execute code, in some cases it could be anything. Take the following proof-of-concept for example, I’m creating a PHP file named as “fake_image.PNG”, and then I include it from a separate file. The include will still treat the fake image file as a PHP file anyway:

root@sinn3r-virtual-machine:/var/www/html# echo "<?php echo 'Hello World'; ?>" > fake_image.PNG
root@sinn3r-virtual-machine:/var/www/html# echo "<?php include('fake_image.PNG'); ?>" > demo.php
root@sinn3r-virtual-machine:/var/www/html# curl http://localhost/demo.php
Hello World

Luckily for the vendor, the exploit doesn’t rely on a include to exploit the payload, instead it relies on an HTTP request like this:

print_status("Making request for '/#{fname}' to execute payload.")
send_request_cgi(
  {
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, 'uploads', 'images', fname)
    },
  15
  )

However, the ability to upload a PHP file with an image extension name is still worth noting from an attacker’s perspective, because it potentially be chained in case of a file inclusion vulnerability in the future.

General Information

Technical Analysis