19 Aug

Authenticated Arbitrary File Upload Vulnerability in Estatik

With our service we notify customers when they are using plugins with known vulnerabilities in the version in use (as well as providing them data on vulnerabilities that existed in other versions of the plugin), but the usefulness of that is somewhat limited since telling you the plugin is vulnerable without being able to update to a fixed version is not the best solution. While we can usually provide someone with a temporary fix until the developer fully fixes the issue, what we really want to do is to make sure it eventually gets fixed so that everyone has a chance to be protected, even if they are not yet using our service.

That often isn’t an easy task. Often times the developers of plugins never responds or fixes the vulnerability when we contact them about it, even in cases where we indicated that vulnerability is already being exploited. When that happens the next step is notify the people running the Plugin Directory, who will the remove the plugin from it pending a fix. At least that is what is supposed to happen, in a number of incidents we have found that plugins have returned without the vulnerability actually being fixed, an issue that at least one person involved with WordPress doesn’t want people to know about.

Another issue that comes up from time to time is that a vulnerability is fixed, but since the related code isn’t fully secured, a lesser vulnerability remains. And then we need to try deal with that, which is what has happened with the plugin Estatik.

Back in July we spotted evidence that hackers may have been targeting the plugin as of June of last year and we found an arbitrary file upload vulnerability in the plugin that would likely be something they would exploit if they knew about it. We contacted the developer about the issue and after waiting for a response or a fix for a week we contacted the Plugin Directory and the plugin was removed. Earlier this week the plugin returned and in checking over the new version, 2.3.0, we found that vulnerability was gone, but the change to fix it left an authenticated arbitrary file upload vulnerability in the plugin. What that means is arbitrary files can still be uploaded, but only by someone that is logged in to WordPress. For a lot of websites, that isn’t much of an issue since many websites only have a single account with the Administrator role, which can usually upload arbitrary files, and other websites only have few trusted users with accounts.

To fix the previous vulnerability the developer removed the following line that allowed those not logged in to access the function es_prop_media_images() through WordPress’s AJAX functionality:

add_action('wp_ajax_nopriv_es_prop_media_images', 'es_prop_media_images');

The plugin continues to make the function accessible via the AJAX functionality when logged in through this line:

add_action('wp_ajax_es_prop_media_images', 'es_prop_media_images');

Since the function is only intended to accessible by Administrator level users there needs to be a check done in the function to make sure the request is not being made by a lower level user. Nothing along that lines was added in 2.3.0.

The plugin is also susceptible to cross-site request forgery (CSRF) based arbitrary file uploading due to a lack of a nonce.

We mentioned these issues when we originally contacted the developer of the plugin, but they apparently ignored it. We have contacted them again about the issue and hopefully we can work with them to get the issue fully resolved.

Proof of Concept

The following proof of concept will upload the selected  file and put it in the current month’s directory inside of the /wp-content/uploads/ director, when logged in to WordPress. The name of the file in the upload directory with be the time the file was saved as output by the function time() followed by a “_” and then name of the as it was uploaded.

Make sure to replace “[path to WordPress]” with the location of WordPress.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="es_prop_media_images" />
<input type="file" name="es_media_images[]" /> 
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 7/25/2016 – Developer notified.
  • 8/19/2016 – Developer notified again.
  • 8/19/2016 – Developer responds.
  • 8/21/2016 – Version 2.3.1 released, which fixes issue.
27 Jun

Authenticated Arbitrary File Upload Vulnerability in WordPress Download Manager

Two weeks ago we found an arbitrary file upload vulnerability in the plugin XData Toolkit. After finding that we wanted to see if there were any very popular plugins that might have similar issue in them. We didn’t find any with such a serious issue, but we did find that the WordPress Download Manger plugin, which has 80,000+ active install according to wordpress.org, does have a more limited arbitrary file upload issue.

When you attempt to upload a file through this plugin that happens through the uploadFile() function in the file /admin/menus/class.Packages.php:

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
function uploadFile(){
	check_ajax_referer('wpdm_admin_upload_file');
	if(!current_user_can('upload_files')) die('-2');
	if(file_exists(UPLOAD_DIR.$_FILES['package_file']['name']) &amp;&amp; get_option('__wpdm_overwrrite_file',0)==1){
		@unlink(UPLOAD_DIR.$_FILES['package_file']['name']);
	}
	if(file_exists(UPLOAD_DIR.$_FILES['package_file']['name']))
		$filename = time().'wpdm_'.$_FILES['package_file']['name'];
	else
		$filename = $_FILES['package_file']['name'];
	move_uploaded_file($_FILES['package_file']['tmp_name'],UPLOAD_DIR.$filename);
	//@unlink($status['file']);
	echo "|||".$filename."|||";
	exit;
}

That checks to make sure that there is a valid nonce, to prevent cross-site request forgery (CSRF), and that the user has the capability to upload_files, which is usually available to Author level users and above. What it doesn’t do is any way restrict what type of files you can upload, so an Author level user could upload .php files with malicious code in them.

If you try to download such a file through the plugin you get the following error message “Invalid File Type (.php)!”, so it doesn’t look like the uploading .php files through the plugin is something the developers intended. They also restrict the downloading of file with .js and .html extensions:

34
if(in_array($ext, array('php', 'js', 'html'))) wp_die("Invalid File Type (.{$ext})!");

Having to be logged in as a Author level user or above limits the threats of this vulnerability and it is further limited by the fact that the directory the files uploaded by the plugin are stored in, /wp-content/uploads/download-manager-files/, contains a .htaccess file that restricts access to the files. That .htaccess could be worked around by combining this with a local file inclusion (LFI) vulnerability to load the file indirectly or with a vulnerability that allows deleting files and deleting the .htaccess file. Its protection also would not exist if you are using Nginx or IIS as your web server software, since those don’t use .htaccess files (both of those web servers are supported by WordPress).

We notified the developer that issues exists as of the current version, 2.8.97, but have not heard back from them so far.

Proof of Concept

Log in as an Author level user, add a new download through the Download menu, and upload a .php file (or other file of your choosing) on that page. You can then find the file at /wp-content/uploads/download-manager-files/.

Timeline

  • 6/20/2016 – Developer notified.
31 May

Authenticated Arbitrary File Upload Vulnerability in Magic Fields

In our previous post about an old arbitrary file upload vulnerability in Magic Fields, we mentioned from reviewing that, that we then noticed that another vulnerability existed.

To recap, in version 1.5.6 of Magic Fields code was added to the file/RCCWP_upload_ajax.php that checked if you were logged in and able at least edit posts, which is capability available to Contributor level users and above, before allowing arbitrary files to be uploaded through the file:

if (!(is_user_logged_in() &amp;&amp;
      (current_user_can('edit_posts') || current_user_can('edit_published_pages'))))
	die(__("Authentication failed!",$mf_domain));

Contributor level users should not be able to upload arbitrary files, so that would mean that while unauthenticated users could no longer upload arbitrary files, there was still an authenticated arbitrary files vulnerability. There is also no protection against cross-site request forgery (CSRF) made with that change.

In the next version, 1.5.7,  a check for a nonce was added, which removed the CSRF vulnerability.

$nonce=$_GET['nonce_ajax'];
if (! wp_verify_nonce($nonce, 'once_ajax_uplooad') ){
  $result = array('error' =&gt; 'Sorry, your nonce did not verify.');
  echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);
  die; 
}

At this point whether there is still a vulnerability would depend on how one can gain access to the nonce being used. The nonce is generated in the file /RCCWP_WritePostPage.php:

117
        var nonce_ajax_upload = "<!--?php echo wp_create_nonce('once_ajax_uplooad') ?-->";

That leads to it being included on any write panel generated from the plugin. So as long as a Contributor or higher level user is able access a write panel they can upload arbitrary files.

Lack of Proper File Validation

While being access the file upload capability when you are not intended to able to do that is a concern. The larger issue here is that there are not restrictions on what types of files are uploaded.

When we started looking into this vulnerability we ran across a support forum thread from someone asking about adding another file extension to the allowed extensions for file uploads. The response from the developer is something that would raise the eyes of most anyone who knows much about web security:

Hi, yes, i will add this extension in the next update. you can add for the moment in this file /js/group.js line 527

Since JavaScript files are usually are only used on the client side, this suggests that the file validation is only being done on the client side and that the user doing the upload has control over what files can be uploaded. That turned out to be the case.

The allowed extensions are defined in /js/group.js on line 527 in version 1.6.3.2 of the plugin:

527
          var allowedExtensions = ["pdf", "doc", "xls", "ppt", "txt", "jpeg", "psd", "jpg", "gif", "png", "docx", "pptx", "xslx", "pps", "zip", "gz", "gzip", "mp3", "aac", "mp4", "wav", "wma", "aif", "aiff", "ogg", "flv", "f4v", "mov", "avi", "mkv", "xvid", "divx","gpx"];

The validation of the file is then done in the file /js/valumsfileuploader.js with the function _validateFile():

397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
 _validateFile: function(file){
 var name, size;
 
 if (file.value){
 // it is a file input 
 // get input value and remove path to normalize
 name = file.value.replace(/.*(\/|\\)/, "");
 } else {
 // fix missing properties in Safari
 name = file.fileName != null ? file.fileName : file.name;
 size = file.fileSize != null ? file.fileSize : file.size;
 }
 
 if ( this._isAllowedExtension(name)){ 
 this._error('typeError', name);
 return false;
 
 } else if (size === 0){ 
 this._error('emptyError', name);
 return false;
 
 } else if (size &amp;&amp; this._options.sizeLimit &amp;&amp; size &gt; this._options.sizeLimit){ 
 this._error('sizeError', name);
 return false;
 
 } else if (size &amp;&amp; size &lt; this._options.minSizeLimit){
 this._error('minSizeError', name);
 return false; 
 }
 
 return true; 
 },

That in turns call the function _isAllowedExtension() in the same file:

446
447
448
449
450
451
452
453
454
455
456
457
 _isAllowedExtension: function(fileName){
 var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
 var allowed = this._options.allowedExtensions;
 
 if (!allowed.length){return true;} 
 
 for (var i=0; i&lt;allowed.length; i++){
 if (allowed[i].toLowerCase() == ext){ return true;} 
 }
 
 return false;
 },

Since that is all running on the client side it can be disabled by a malicious user and therefore they can upload malicious files.

What then was surprising to find out is that there is actually the code needed to validate the file and restrict the allowed extensions on the server side. On the server side the file is uploaded through the file /RCCWP_upload_ajax.php and that has a line to define allowed extensions, but it is empty:

174
175
// list of valid extensions, ex. array("jpeg", "xml", "bmp")
$allowedExtensions = array();

Proof of Concept

Logged in as user that has access to a Magic Fields write panel, view the source code of a write panel page and find the line that begins “var nonce_ajax_upload”. Replace “[nonce value]” in the proof of concept with the value of “nonce_ajax_upload” on that line.

Also, make sure to replace “[path to WordPress]” with the location of WordPress:

<html>
<head>
</head>
<body>
<form action="http://[path to WordPress]/wp-content/plugins/magic-fields/RCCWP_upload_ajax.php?nonce_ajax=[nonce value]" method="post" enctype="multipart/form-data">
<input name="qqfile" type="file" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 5/20/2016 – Notified developer.
  • 5/31/2016 – Notified wordpress.org Plugin Directory.
  • 6/1/2016 – Version 1.7 released (not available on Plugin Directory), which fixes vulnerability.
12 May

Authenticated Arbitrary File Upload Vulnerability in WP Editor

To stay on top of vulnerabilities in WordPress plugin for you, we monitor a number of different sources. One of them is hacking attempts on our websites, which mostly identifies fairly old vulnerabilities that we haven’t yet included in our data. In the case of a one vulnerability from back in 2012 we discovered that the vulnerability had never been fixed and was still in the Plugin Directory. Yesterday that monitoring lead us to seeing evidence that the WP Editor plugin is being exploited and finding a couple of serious vulnerabilities that could be what they are exploiting.

We have started seeing requests for the file /wp-content/plugins/wp-editor/js/wpeditor.js, which based on the files being requested alongside it, looks like the request are to check to see if the plugin is in use and if so then the hacker would likely try to exploit it. Since we don’t have the plugin installed the exploitation attempt didn’t happen, so we don’t know what they are trying to exploit. So then after looking for any public reports of vulnerabilities in the plugin we starting to reviewing the plugin and quickly found a couple of serious security vulnerabilities in the current version of the plugin 1.2.5.3.

The first vulnerability is that any logged in user can upload arbitrary files to the website since there is no check as to the user capabilities when doing that. There is also no protection against cross-site request forgery (CSRF), so it is also susceptible to that.

We notified the Plugin Directory shortly before 5PM MDT yesterday about the appearance hacking attempts and the vulnerabilities we had found. Despite the serious nature, as now we haven’t received any response from them and the plugin is still available in the Plugin Directory, which indicates that they have not processed our message because once that is done they will usually remove the plugin pending a fix.

The plugin hasn’t been updated in 8 months, so it isn’t clear if it still being supported by the developer anymore, but we are in the process of trying to notifying them.

In the meantime we have added the vulnerabilities to our service’s data, so customers will start getting notified when the next check runs. We have also added it to the data in our companion Plugin Vulnerabilities plugin, so even you are not using our service yet you can get alerted to vulnerability.

Proof of Concept

The following proof of concept will upload the chosen file to the root directory of the WordPress installation, when logged in as a subscriber level or higher user.

Make sure to replace “[path to WordPress]” with the location of WordPress:

<html>
<head>
</head>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="action" value="upload_files" />
<input type="hidden" name="current_plugin_root" value="../" />
<input type="hidden" name="directory" value="" />
<input type="file" name="file-0" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

5/11/2016- WordPress.org Plugin Directory notified.
5/12/2016 – Developer notified.
5/13/2016 – Version 1.2.6 released, which fixes vulnerabilities.