Unknown
CMS Made Simple (CMSMS) Showtime2 Post Auth Arbitrary File Upload Vulnerability
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
Unknown
(1 user assessed)Unknown
(1 user assessed)Unknown
Unknown
Unknown
CMS Made Simple (CMSMS) Showtime2 Post Auth Arbitrary File Upload Vulnerability
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
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
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.
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportGeneral Information
References
Miscellaneous
Additional Info
Technical Analysis
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: