Critical Vulnerability Patched in UserPro Plugin
Security Advisories
Featured
UserPro
Critical Vulnerability
privilege escalation
account takeover
Published 22 May 2024 Rafie Muhammad Security Researcher at Patchstack Table of Contents
UserPro
Unauthenticated Account Takeover
This blog post is about the UserPro plugin vulnerabilities. If you’re a UserPro user, please update the plugin to at least version 5.1.9.
All paid Patchstack users are protected from this vulnerability. Sign up for the free Community account first, to scan for vulnerabilities and apply protection for only $5 / site per month with Patchstack. For plugin developers, we have security audit services and Enterprise API for hosting companies.
About the UserPro Plugin
The plugin UserPro (premium version), which has over 20,000 sales, is known as the more popular community and user profile plugin in WordPress. This plugin is developed by DeluxeThemes.
This premium WordPress plugin is a full-featured user profile and community plugin. Users are able to create front-end user profiles and community sites in WordPress using UserPro. It comes packed with features like customizable login and registration forms and social connect integration.
The security vulnerability
This plugin suffers from an unauthenticated account takeover vulnerability. This allows any unauthenticated users to change the password of any users with certain conditions. The described vulnerability was fixed in version 5.1.9 and assigned CVE-2024-35700.
The underlying vulnerability exists on userpro_process_form
function:
function userpro_process_form()
{
global $userpro;
/* Security do not use noonce to non-looged in users */
$template=$_POST['template'];
if ( $template !=='login' && $template !=='register' ) {
if ( ! check_ajax_referer( 'user_pro_nonce', 'nonce', false ) ) {
wp_send_json_error( 'Invalid nonce.' );
die();
}
}
if (!isset($_POST) || $_POST['action'] !='userpro_process_form') {
die();
}
if (!userpro_is_logged_in() && $_POST['template']=='edit') {
die();
}
/* Form */
$form=[];
foreach ($_POST as $key=> $val) {
$key=explode('-', $key);
$key=$key[0];
$form[$key]=$val;
}
$shortcode=$_POST['shortcode'];
$user_id=isset($form['user_id']) ? $form['user_id'] : '';
/* Runs before a form is processed */
do_action('userpro_before_form_save', $form);
$output=[];
/* PROCESSING ACTIONS */
switch ($template) {
------- CUT HERE -------
/* change pass */
case 'change':
$output['error']=[];
if (!$form['secretkey']) {
$output['error']['secretkey']=__('You did not provide a secret key.', 'userpro');
}
/* Form validation */
/* Here you can process custom "errors" before proceeding */
$output['error']=apply_filters('userpro_form_validation', $output['error'], $form);
if (empty($output['error'])) {
$users=get_users([
'meta_key'=> 'userpro_secret_key',
'meta_compare'=> 'EXISTS', // Check if the key exists in metadata.
]);
$key_matched=false;
$user_id=null;
foreach ($users as $user) {
$stored_hashed_key=get_user_meta($user->ID, 'userpro_secret_key', true);
// Verify the input key directly without additional manipulation
if (wp_check_password($form['secretkey'], $stored_hashed_key)) {
$key_matched=true;
$user_id=$user->ID;
break; // Exit the loop as we found a matching key
}
}
if (!$users[0]) {
$output['error']['secretkey']=__('The secret key is invalid or expired.', 'userpro');
} else {
add_filter('send_password_change_email', '__return_false');
$user_id=$users[0]->ID;
wp_update_user(['ID'=> $user_id, 'user_pass'=> $form['user_pass']]);
delete_user_meta($user_id, 'userpro_secret_key');
add_action('userpro_pre_form_message', 'userpro_msg_login_after_passchange');
$shortcode=stripslashes($shortcode);
$modded=str_replace('template="change"', 'template="login"', $shortcode);
$output['template']=do_shortcode($modded);
if (userpro_get_option('notify_user_password_update')=="1") {
userpro_mail($user_id, 'passwordchange', $form['user_pass']);
}
}
}
break;
------- CUT HERE -------
This function itself is hooked to the wp_ajax_nopriv_userpro_process_form function making it accessible by unauthenticated users. The above case handles the process of changing the user’s password. Let’s try to analyze the process.
First, the function will construct a $users object using the get_users function specifying the “meta_key” to “userpro_secret_key”. This indicates that the object will contain any users on the database that have the “userpro_secret_key” metavalue set to the account. The value itself is just a secret key that will be generated when users try to change their password and will be used to verify the password change process.
The function then performs a loop to check if the supplied $form[‘secretkey’] value is the same as the $stored_hashed_key value (which is the user’s “userpro_secret_key”) using the wp_check_password and it will set the $key_matched variable to true and set the $user_id variable to the matched user’s id.
The above check seems legit, however, the next step in the function is checking if $users[0] exists and it will set the $user_id variable to the $users[0]->ID. It will then perform a wp_update_user function, changing the $users[0] (the first entry of users that have the “userpro_secret_key” set) password with the supplied $form[‘user_pass’] value.
With this condition, any unauthenticated users are able to change the password of any user that has a “userpro_secret_key” value set. The scenario of exploitation would be ideally performed after a user requests a password change via the forgot password feature and before the user actually changes their password.
Note that this vulnerability is reproducible in a default installation and activation of the UserPro plugin without a specific requirement or configuration.
The patch
The vendor applies a patch by first checking if the new password is the same as the current one. Then, the code will directly use the $user_id variable that has been set from the previous check using the wp_check_password function.
Conclusion
The vulnerabilities discussed here underscore the importance of securing all aspects of a plugin, especially those designed for changing the user’s password. Always make sure the object or variable passed to the crucial function to update the user’s password has been validated and previously checked.
Found this useful? Share on
Detect vulnerabilities and protect your WordPress websites. See features
Weekly security advice
Get the latest WordPress security intelligence delivered to your inbox. NEW: Get started with 5 bite-sized security lessons.
Email Signup Subscribe
The latest in Security Advisories
Exploring the Unknown: Beneath the Surface of Unpatched WordPress SSRF
WordPress core
ssrf
14 May, 2024
Critical Vulnerabilities Found in XStore Theme and Plugin
arbitrary option update
XStore
Critical Vulnerability
account takeover
php object injection
High Priority Vulnerabilities Patched in Uncode Core Plugin
plugin
Uncode
arbitrary file deletion
Protection
Pricing WordPress For agencies Standard API Documentation
Solutions
WordPress security Plugin auditing Managed VDP Bug bounty Enterprise API
Bug bounty
Leaderboard Security programs Guidelines Report
Resources
Vulnerability database WordPress statistics
Patchstack
Careers Media kit LinkedIn Facebook X © 2024 Patchstack DPA Privacy Policy Terms & Conditions
Protection
Pricing WordPress For agencies Standard API Documentation
Solutions
WordPress security Plugin auditing Managed VDP Bug bounty Enterprise API
Bug bounty
Leaderboard Security programs Guidelines Report
Resources
Vulnerability database WordPress statistics
Patchstack
Careers Media kit LinkedIn Facebook X © 2024 Patchstack DPA Privacy Policy Terms & Conditions
This website uses cookies. Learn more.
Looks like your browser is blocking our support chat widget. Turn off adblockers and reload the page. Reload page
close chevron-down chain bars angle-right angle-up cross menu
Source: patchstack.com