25 Nov 2022

WordPress Plugins Failing to Include Needed Capabilities Check for AJAX Accessible Functionality

When reviewing security changes being made in WordPress plugins used by our customers, it isn’t uncommon for us to find that developers have failed to fully fix the vulnerabilities, or as was the case recently with a plugin with 300,000+ installs, the have failed to fix the vulnerability at all. What we also are often seeing is that plugins are missing a basic security check when having plugin functionality accessible through WordPress’ AJAX system.

One recent example of that we ran across involved a plugin named Log HTTP Request. It registers two functions accessible through WordPress AJAX functionality to anyone logged in to WordPress:

48
49
add_action( 'wp_ajax_lhr_query', [ $this, 'lhr_query' ] );
add_action( 'wp_ajax_lhr_clear', [ $this, 'lhr_clear' ] );

Those functions are intended to be accessed by someone accessing the plugin’s admin page, which is limited to users with the manage_options capability:

85
add_management_page( 'Log HTTP Requests', 'Log HTTP Requests', 'manage_options', 'log-http-requests', [ $this, 'settings_page' ] );

Normally, only users with the Administrator role have that capability.

Taking a look at one of the functions, there was a check for valid nonce to prevent cross-site request forgery (CSRF) using the function check_ajax_referer():

117
118
119
120
121
function lhr_clear() {
	check_ajax_referer( 'lhr_nonce' );
 
	LHR()->query->truncate_table();
}

What is missing there is a check to make sure that only the intended users have access to that. Often a nonce check would restrict other users from accessing those things, since they would not normally have access to the relevant nonce, but the WordPress documentation for nonces makes it clear that it isn’t intended for that purpose:

Nonces should never be relied on for authentication, authorization, or access control. Protect your functions using current_user_can(), and always assume nonces can be compromised.

It also isn’t always the case that the nonce is restricted.

The fix for this is simple. The function current_user_can() provides the ability to check if a user has a given capability. This plugin addressed by checking if the user making the request relevant capability and if not, it exits:

104
105
106
if ( ! current_user_can( 'manage_options' ) ) {
	wp_die();
}

Leave a Reply

Your email address will not be published. Required fields are marked *