13 Feb

Our Proactive Monitoring Caught an Authenticated Arbitrary File Upload Vulnerability in Church Admin

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 version 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 authenticated arbitrary file upload vulnerability we found in the plugin Church Admin. This vulnerability could have allowed someone that has access to a WordPress account that can access the admin area (which would normally be any user, Subscriber-level and above) to upload a malicious file to the website, which could they use to take additional actions on 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 occurred in the function church_admin_bible_reading_plan(), which is located in the file /app/app-admin.php and would save any type of file sent with a request to the current year/month’s directory inside of the /wp-content/upload/ directory:

function church_admin_bible_reading_plan()
 global $wpdb;
 echo'<h2 class="plan-toggle">'.__('Which Bible Reading plan? (Click to toggle)','church-admin').'</h2>';
 echo'<div class="bible-plans" style="display:none">';
 echo '<p>'.__('The Bible reading post type for a particular day takes priority over any plan loaded below','church-admin').'</p>';
 if(!empty($_FILES) && $_FILES['file']['error'] == 0)
 $wpdb->query('TRUNCATE TABLE '.CA_BRP_TBL);
 $filename = $_FILES['file']['name'];
 $upload_dir = wp_upload_dir();
 $filedest = $upload_dir['path'] . '/' . $filename;
 if(move_uploaded_file($_FILES['file']['tmp_name'], $filedest))echo '<p>'.__('File Uploaded and saved','church-admin').'</p>';

That function gets called in the function church_admin_app() if the website has a license number for the companion app set up:

function church_admin_app()

	global $wpdb;
	echo'>h1<Church Admin App Admin>/h1<';
		//no licence yet
		echo '>div id="iphone" class="alignleft"<>iframe src="'.plugins_url('/app/demo/index.html',dirname(__FILE__) ).'" width=475 height=845 class="demo-app"<>/iframe<>/div<';
		if(!empty($_POST['app-licence']) && $_POST['app-licence']==md5('licence'.site_url()))
			update_option("church_admin_app_groups",">h2<Small groups>/h2<");
		echo'>h3<'.__('If you have subscribed, please fill in this form to activate','church-admin').'>/h3<>form action="" method="post"<>table<>tr<>th scope="row"<'.__('App Licence Key','church-admin').'>/th<>td<>input type="text" name="app-licence"/<>/td<>/tr<>tr<>th scope="row"<App ID>/th<>td<>input type="text" name="app-id"/<>/td<>/tr<>tr<>td colspacing=2<>input type="submit" value="'.__('Activate','church-admin').'"/<>/td<>/tr<>/table<>/form<';
		echo'>h3<'.__('Try out the app...','church-admin').'>/h3<>p<
>a href="https://itunes.apple.com/gb/app/wp-church/id1179763413?mt=8"<'.__('Install app on your iPhone now','church-admin').'>/a< and >a href="https://play.google.com/store/apps/details?id=com.churchadminplugin.wpchurch"<Android>/a<>/p<';


An attacker can set that up that license number from the page shown if that hasn’t already been set up. They don’t even need to sign up for the service, as the value is just the md5 value of the website’s site_url:


The function church_admin_app() is in turn accessible from the function church_admin_main() (located in the file /index.php):

case 'app': require_once(plugin_dir_path(__FILE__).'app/app-admin.php');church_admin_app();break;

Which is accessible to anyone with the “read” capability:

add_menu_page('church_admin:Administration', __('Church Admin','church-admin'),  'read', 'church_admin/index.php', 'church_admin_main');

We notified the plugin’s developer of the issue yesterday and they made changes that while not ideal, do fix the vulnerability. The function church_admin_bible_reading_plan() has now been restricted to those with the ability to “manage_options” (it seems that would be better suited to be a restriction placed in the function church_admin_app() though), which would normally limit it to only Administrators:

function church_admin_bible_reading_plan()
	global $wpdb;
	$current_user = wp_get_current_user();
 if(is_user_logged_in()&& current_user_can('manage_options'))

In that function there has also been a nonce check added, which would prevent cross-site request forgery (CSRF), and a check of what type of files has been uploaded:

if(!empty($_POST['save_csv'])&& check_admin_referer( 'bible_upload', 'nonce' ) )
	$mimes = array('application/vnd.ms-excel','text/plain','text/csv','text/tsv');
	if(!empty($_FILES) && $_FILES['file']['error'] == 0 && in_array($_FILES['file']['type'],$mimes))

The [‘type’] attribute of $_FILES is user specified so it shouldn’t be used a security check, but in this case, properly limiting the upload to Administrators and protecting against CSRF is enough protection.

There are some other upload functions in the plugin that could use a close check to make sure they are properly secured (something we mentioned to the developer).

Proof of Concept

The following proof of concept will upload the selected file to the current year/month’s directory inside of the /wp-content/upload/ directory, when logged in to WordPress and the license number set for the companion app set.

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

<form action="http://[path to WordPress]/wp-admin/admin.php?page=church_admin%2Findex.php&action=app" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" name="save_csv" value="Submit" />


  • February 12, 2018 – Developer notified.
  • February 12, 2018 – Developer responds.
  • February 12, 2018 – Version 1.2540 release, which fixes vulnerability.
22 Nov

Authenticated Arbitrary File Upload Vulnerability in Vmax Project Manager

A month ago we wrote about how the security review of newly submitted plugins to the WordPress Plugin Directory needs improvement. One of the newly introduced plugins that lead to that was the plugin Vmax Project Manager. We came across the plugin through our proactive monitoring of changes made to plugins to try to catch serious vulnerabilities, due to the possibility of an arbitrary file upload vulnerability in the plugin.

What got flagged during our monitoring was the following line in the file /model/function.php:
move_uploaded_file($_FILES['file_name']['tmp_name'], $upload_dir['basedir'] . '/vpm/project_file/' . $_FILES["file_name"]["name"]);

That alone doesn’t tell you much, as all you can tell is it looks like an uploaded file is being saved to the filesystem with its filename intact.

With the fuller context of the function it is in you can see that there are no checks being done there that might restrict what types of files might be uploaded:

function vpm_file_create() {
	$data['vpm_project_id']	=	sanitize_text_field($_POST['vpm_project_id']);
	$data['file_name']		=	$_FILES['file_name']['name'];
	$data['note']			=	sanitize_text_field($_POST['note']);
	$upload_dir = wp_upload_dir();
    move_uploaded_file($_FILES['file_name']['tmp_name'], $upload_dir['basedir'] . '/vpm/project_file/' . $_FILES["file_name"]["name"]);

There still could be code elsewhere that restricts what type of files can be uploaded, though more often than not the code doing any checks would be in that.

That still doesn’t mean there is a vulnerability as the code still needs to be accessible in a way that it can be abused.

That function is called in one place, in the file /controller/controller.php:

	else if($task	==	'create_file') {

That value of $task comes from the beginning of the file, which also checks for a valid nonce:

$nonce 	=	sanitize_text_field($_POST['nonce']);
$task 	=	sanitize_text_field($_POST['task']);
$nonce_verify	=	wp_verify_nonce($nonce , 'vpm-project-manager');
// Only if nonce values submitted with post calls are verified, those db query functions will be executed
if ($nonce_verify == true) {

At that point we went to look at the issue from inside WordPress, though we later went to back to tracing the code used and spotted another vulnerability in the plugin, which will be the subject of another post.

On the plugins’ page to manage a project there is a section for files:

That page is available to anyone with the “read” capability, which is a capability that provides access to Admin dashboard and is a capability provided to Subscriber-level users and above (in the file /vpm.php):

$menu = add_menu_page('Vpm Project Manager', 'Project Manager', 'read', 'vpm-project', 'vpm_project' );

When you click Create you are taken to a page to upload a file:

If you try to upload a file with a .php extension that is not stopped:

So anyone that is logged in to WordPress with the “read” capability can upload arbitrary files.

We notified the developer of the issue on October 18. They responded five days later and said that it would be fixed within two weeks, but so far no new version of the plugin has been released. In line with our disclosure policy, which is based on the need to provide our customers with information on vulnerabilities on a timely basis, we are now disclosing this vulnerability.


  • October 18, 2017 – Developer notified.
  • October 23, 2017 – Developer responds.
02 Oct

Authenticated Arbitrary File Upload Vulnerability in WordPress Book List

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 an authenticated arbitrary file upload vulnerability in WordPress Book List.

The plugin allows users with the “manage_options” capability, which would normally only be Administrators, the ability to access an admin page to upload StylePaks for the plugin.

The uploads are handle through the function wpbooklist_upload_new_stylepak_action_callback(), which is available through WordPress AJAX functionality to those logged in to WordPress as well as those not logged in (in the file /wpbooklist.php):

add_action( 'wp_ajax_wpbooklist_upload_new_stylepak_action', 'wpbooklist_upload_new_stylepak_action_callback' );
add_action( 'wp_ajax_nopriv_wpbooklist_upload_new_stylepak_action', 'wpbooklist_upload_new_stylepak_action_callback' );

The first thing that function does is check for a valid nonce (in the file /includes/ajaxfunctions.php):

function wpbooklist_upload_new_stylepak_action_callback(){
	global $wpdb;
	check_ajax_referer( 'wpbooklist_upload_new_stylepak_action_callback', 'security' );

That value of the nonce is provided through the function wpbooklist_upload_new_stylepak_action_javascript(), which runs when any admin page is loaded:

add_action( 'admin_footer', 'wpbooklist_upload_new_stylepak_action_javascript' );

So anyone logged in to WordPress that can access admin pages has access to a valid nonce, despite the user interface for the uploads only being accessible to Administrators.

Returning to the function wpbooklist_upload_new_stylepak_action_callback(), it provides no check to limit what types of users can access it, so as long as someone has access to a valid nonce they can access its functionality. If there is not a POST input “stylepak” then the following line of code will run in the function, which will upload a file without any restriction as to the file type:

$move_result = move_uploaded_file($_FILES['my_uploaded_file']['tmp_name'], LIBRARY_STYLEPAKS_UPLOAD_DIR."{$_FILES['my_uploaded_file'] ['name']}");

We received a response from the developer the same day we contacted them, asking who we were. Several versions of the plugin have been release since then, but the vulnerability has not been fixed. In line with our disclosure policy, which is based on the need to provide our customers with information on vulnerabilities on a timely basis, we are now disclosing this vulnerability.

Proof of Concept

The following proof of concept will upload the selected file to the directory /wp-content/uploads/wpbooklist/stylepaks/library/.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[valid nonce]” with a valid nonce, which is value of the security element in the below the element that can be found below the line “‘action’: ‘wpbooklist_upload_new_stylepak_action’,” on any admin page.

<form action="http://[path to WordPress]/wp-admin/admin-ajax.php" method="POST" enctype="multipart/form-data">
<input type="hidden" name="action" value="wpbooklist_upload_new_stylepak_action" />
<input type="hidden" name="security" value="[valid nonce]" />
<input type="file" name="my_uploaded_file" />
<input type="submit" value="Submit" />


  • September 1, 2017 – Developer notified.
18 Sep

Authenticated PHP Object Injection Vulnerability in Post Pay Counter

Through the proactive monitoring of changes in WordPress plugins for serious vulnerabilities we do, we have found some of those serious vulnerabilities, but we also have found less serious variants of some of those vulnerabilities. The latter is certainly the case with an authenticated PHP object injection vulnerability we found in the plugin Post Pay Counter.

On the plugin’s Options page there is a section title Import/Export Settings. When doing an import through that the function import_settings(), which began as follows in version 2.730, is utilized (in /classes/ppc_ajax_functions_class.php):

static function import_settings() {
	global $ppc_global_settings;
	self::ppc_check_ajax_referer( 'ppc_import_settings' );
	$to_import = unserialize( base64_decode( $_REQUEST['import_settings_content'] ) );

That code checked for a valid nonce, which prevents cross-site request forgery (CSRF), and then passed the value of the GET or POST input “import_settings_content” through the unserialize() function, which permitted PHP object injection.

The value of the nonce is only available on the plugin’s Options page. Normally that page is only available to Administrators, for who this really wouldn’t be a vulnerability since they can normally do the equivalent what this vulnerability could do. The plugin does provide the option to allow lower users level access to it and for them to exploit this it would be a vulnerability:

Before doing the importation of settings, the plugin should have also made sure the user is intended to be able to import settings by checking if their user role is one permitted to access the page.

Less than two days after we notified the developer of the issues they released version 2.731, which replaces the usage of unserialize():

$to_import = json_decode( base64_decode( $_REQUEST['import_settings_content'] ), true );

and adds a capabilities check to the function that handles updating the settings, update_settings() (in the file /classes/ppc_save_options_class.php):

if( ! current_user_can( $ppc_global_settings['cap_manage_options'] ) )
	return new WP_Error( 'save_settings_error', __( 'Are you sure you want to do this?' ) );

Proof of Concept

With our plugin for testing for PHP object injection installed and activated, passing “TzoyMDoicGhwX29iamVjdF9pbmplY3Rpb24iOjA6e30=” through the Import Settings feature on /wp-admin/admin.php?page=ppc-options will cause the message “PHP object injection has occurred.” to be shown.


  • September 14, 2017 – Developer notified.
  • September 15, 2017 – Developer responds.
  • September 16, 2017 – Version 2.731, which fixes vulnerability.
13 Sep

Authenticated Arbitrary File Upload Vulnerability in Football Pool

Through the proactive monitoring of changes in WordPress plugins for serious vulnerabilities we do, we recently found an authenticated arbitrary file upload vulnerability in the Football Pool plugin.

The plugin has a number of admin pages that are available to users with the ‘manage_football_pool’ capability. The plugin creates a new role with that capability as well as providing it to Editor and Administrator-level users (in the file /classes/class-football-pool.php):

add_role( 'football_pool_admin', 'Football Pool Admin', 
				'read' => true,
				'manage_football_pool' => true,
$role = get_role( 'administrator' );
if ( ! is_null( $role ) ) $role->add_cap( 'manage_football_pool' );
$role = get_role( 'editor' );
if ( ! is_null( $role ) ) $role->add_cap( 'manage_football_pool' );

Through the Matches admin page the plugin provides a upload capability intended to upload CSV files.

When the upload occurs a valid nonce is checked before the function that handles that runs, which prevents cross-site request forgery (CSRF) (in the file /admin/class-football-pool-admin-games.php):

check_admin_referer( FOOTBALLPOOL_NONCE_ADMIN );
$uploaded_file = self::upload_csv();

As of version 2.6.3, the function didn’t include any restriction on what type of files can be uploaded:

private static function upload_csv() {
	$err = false;
	if ( is_uploaded_file( $_FILES['csv_file']['tmp_name'] ) ) {
		$new_file = FOOTBALLPOOL_CSV_UPLOAD_DIR . $_FILES['csv_file']['name'];
		if ( move_uploaded_file( $_FILES['csv_file']['tmp_name'], $new_file ) === false ) {

Earlier today we notified the developer of the plugin of the vulnerability and they promptly released a new version, 2.6.4, which limits what can be uploaded to only files with .csv and .txt extensions:

private static function upload_csv() {
	$err = false;
	$msg = '';
	if ( is_uploaded_file( $_FILES['csv_file']['tmp_name'] ) ) {
		$new_file = FOOTBALLPOOL_CSV_UPLOAD_DIR . $_FILES['csv_file']['name'];
		$extension = pathinfo( $new_file, PATHINFO_EXTENSION );
		if ( in_array( $extension, array( 'csv', 'txt' ) ) ) {
			if ( move_uploaded_file( $_FILES['csv_file']['tmp_name'], $new_file ) === false ) {

Proof of Concept

  1. Log in WordPress as a user with the role Football Pool Admin.
  2. Visit the Matches page of the plugin, /wp-admin/admin.php?page=footballpool-games.
  3. Click the “Bulk change game schedule” button in the top right of the page.
  4. In the “Upload new game schedule” section of the page, chose a file and click the “Upload CSV” button.
  5. The file will be uploaded to the directory /wp-content/uploads/football-pool/schedules/.


  • September 13, 2017 – Developer notified.
  • September 13, 2017 – Developer responds.
  • September 13, 2017 – Version 2.6.4 released, which fixes vulnerability.
14 Jun

Vulnerability Details: Authenticated File Manager Access Vulnerability in File Manager

From time to time a vulnerability is fixed in a plugin without the discoverer putting out a report on the vulnerability and we will put out a post detailing the vulnerability so that we can provide our customers with more complete information on the vulnerability.

While looking into a security fix just made to file manager plugin WP File Manager we found that the ...

To read the rest of this post you need to have an active account with our service.

For existing customers, please log in to your account to view the rest of the post.

If you are not currently a customer, when you sign up now you can try the service for half off (there are a lot of other reason that you will want to sign up beyond access to posts like this one).

If you are a WordPress plugin security researcher please contact us to get free access to all of our Vulnerability Details posts.

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.

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


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

function uploadFile(){
	if(!current_user_can('upload_files')) die('-2');
	if(file_exists(UPLOAD_DIR.$_FILES['package_file']['name']) && get_option('__wpdm_overwrrite_file',0)==1){
		$filename = time().'wpdm_'.$_FILES['package_file']['name'];
		$filename = $_FILES['package_file']['name'];
	echo "|||".$filename."|||";

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:

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


  • 6/20/2016 – Developer notified.
  • 3/17/2017 – Version 2.9.46 released, which fixes vulnerability.
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.