Privilege Escalation
Introduction
This article covers cases of possible Privilege Escalation on WordPress, including several processes inside of the plugin/theme that can be used to escalate user privilege or role. Privilege escalation in this case also includes an account takeover case.
Arbitrary Option Update
WordPress has an Options
feature which is a simple and standardized way of storing data in the database. It makes it easy to create, access, update, and delete options. All the data is stored in the wp_options
table under a given custom name.
Note that the site functions are essentially the same as their counterparts. The only differences occur for WP Multisite when the options apply network-wide and the data is stored in the wp_sitemeta
table under the given custom name.
Two of the default options available are the users_can_register
and default_role
options. The users_can_register
option itself will decide if the site accepts an open user registration, by default this option is disabled. The default_role
itself is an option to decide the default role for the new user upon registration on the site and has a default value of the subscriber
role.
To update an option, the update_option
function is used. If a user can fully control the $option
and the $value
parameters, they can achieve a privilege escalation by enabling the users_can_register
option and setting the default_role
option to administrator
so the registration feature is open and anyone can register with an administrator role.
Example of vulnerable code:
To exploit this, any unauthenticated user just needs to perform a POST request to the admin-ajax.php
endpoint specifying the needed action and parameter to trigger the update_option
function.
After that, the user could just simply go to <WORDPRESS_BASE_URL>
/wp-login.php?action=register and register an account with an administrator role.
Below are some of the findings related to Arbitrary Option Update:
Arbitrary User Meta Update
According to official documentation, the WordPress users
table was designed to contain only the essential information about the user. Because of this, to store additional data, the usermeta
table was introduced, which can store any arbitrary amount of data about a user.
WordPress stores each of the user role data inside of the usermeta
table with the key wp_capabilities
. The value inside of this meta is an array value which is stored as a serialized object. Example of value inside of this meta key:
To update the user’s meta, the update_user_meta
function is used. If a user can fully control the $meta_key
and the $meta_value
parameters, they can achieve a privilege escalation by either updating their account if the $user_id
parameter can’t be controlled or any account if the user can control the $user_id
parameter.
Example of vulnerable code:
To exploit this, any authenticated user just needs to perform a POST request to the admin-ajax.php
endpoint specifying the needed action and parameter to trigger the update_user_meta
function.
Below are some of the findings related to Arbitrary User Meta Update:
Unrestricted User Registration
Many plugins or themes implement a custom user registration process. This case is mostly found in a page builder plugin as one of the shortcode or block features.
One of the ways to register a user is through a wp_insert_user
function. This function accepts the $userdata
parameter which is an array, object, or WP_User
object of user data arguments. One of the values inside of the parameter is role
which determines the role of the inserted user. Another value would be meta_input
which can be filled with custom user metadata which can also result in a privilege escalation.
Example of vulnerable code:
To exploit this, any unauthenticated user just needs to perform a POST request to the admin-ajax.php
endpoint specifying the needed action and parameter to trigger the wp_insert_user
function.
Below are some of the findings related to Unrestricted User Registration:
- Vulnerability In Houzez Theme Exploited in The Wild
- Multiple Vulnerabilities Patched in Themify Ultra Theme
- Two Paths to Privilege Escalation Vulnerability In The Simple Membership Plugin
- Authenticated Privilege Escalation Vulnerability in Essential Addons for Elementor
- Critical Privilege Escalation in HT Mega Plugin Affecting 100k+ Sites
- Critical Privilege Escalation in Essential Addons for Elementor
Unrestricted User Update
Similar to the above case, instead of the user registration process, this case involves updating data on the user’s main table. This process is mostly found in a plugin or theme that has a custom feature to update the user’s main data such as first name, last name, description, etc.
One of the ways to update a user’s main data is through a wp_update_user
function. This function accepts the $userdata
parameter which is an array, object, or WP_User
object of user data arguments. One of the values inside of the parameter is role
which determines the role of the inserted user. Another value would be meta_input
which can be filled with custom user metadata which can also result in a privilege escalation.
Example of vulnerable code to escalate own account role:
To exploit this, any authenticated user just needs to perform a POST request to the admin-ajax.php
endpoint specifying the needed action and parameter to trigger the wp_update_user
function and change their role.
It is also possible to update a user’s password by specifying the user_pass
value in the $userdata
parameter array. This could also lead to account takeover if we can control the ID
value on the $userdata
parameter to specify the targeted user.
Other than that, an attacker can also modify user_activation_key
, user_email
, or user_login
to possibly take over their account.
Insecure Password Reset
Similar to the custom registration process, many plugins or themes also implement a custom reset password process. This case is mostly found in a page builder plugin as one of the shortcode or block features.
One of the ways to reset a user’s password is through a reset_password
function. This function accepts the $user
parameter as the targeted user object and the $new_pass
parameter as the new password value for the user.
The function itself is just a wrapper to another function wp_set_password
which is the core function to set the new password for the user. This function accepts the $user_id
parameter as the targeted user ID and the $password
parameter as the new password value for the user.
Example of vulnerable code:
To exploit this, unauthenticated users just need to craft and serve a malicious HTML file and trick privileged users into visiting the HTML file to do a reset password action with an attacker-controlled password value.
Below are some of the findings related to Insecure Password Reset:
- Two Paths to Privilege Escalation Vulnerability In The Simple Membership Plugin
- Critical Easy Digital Downloads Vulnerability
Insecure Authentication Cookie Set
In some cases, the developer needs to log in as a user with a custom identifier or process. This process is mostly seen in a custom login with a third-party process, where users only need to specify some kind of unique identifier or token to be able to log in to a WordPress site.
This process of setting up the unique identifier or token to the login process many times is implemented improperly, resulting in the attacker being able to log in to any user’s account by supplying a guessable or known identifier or even setting up the identifier themself.
One of the functions that could be used to log in a user is the wp_set_auth_cookie
function. This function sets the authentication cookies based on the user ID. We only need to set the targeted user id through the $user_id
parameter and WordPress will return the authentication cookie, basically allowing the user to log in to the targeted user’s account.
Example of vulnerable code:
To exploit this, any unauthenticated user just needs to perform a POST and GET request to the admin-ajax.php
endpoint specifying the needed action and parameter to trigger the configure_platform_callback
function and then the login_third_party
function.
Below are some of the findings related to Insecure Authentication Cookie Set:
Insecure Current User Object Set
Just like the wp_set_auth_cookie()
mentioned above, the wp_set_current_user()
function in WordPress is responsible for setting the current user object. It is typically used when WordPress needs to impersonate a different user within the same session. The function takes a user ID, email, or username as input and sets the current user to the one that matches.
While this function is essential for WordPress’ user management system, incorrect usage or insufficient validation around it can lead to several vulnerabilities, including privilege escalation, user impersonation, and authentication bypass.
As mentioned in the WordPress Developer Resources, this function takes either a user id or a username.
So we can supply a user ID or a username like the following.
Well unlike wp_set_auth_cookie
, wp_set_current_user()
doesn’t log the user in. It just sets the global user variables. The only way for an attacker to log in to an account is if it is supported by wp_set_auth_cookies
and a wp_login
action calls.
Does that mean that is the only way to exploit this? No. Just like mentioned in the Critical Privilege Escalation in LiteSpeed Cache Plugin Affecting 5+ Million Sites it is possible to call /wp-json/wp/v2/users
REST API to generate a new user with high privileges.
Other custom functionality that is relaying on capability checks will also be bypassed.
In this setting since we set our capabilities to a user with the user ID 1 (which is highly likely an administrator) we can pass through the capability check to access the internal functionality.
In this example code, we can call the function using cURL like this to set our capabilities and add a new user.
Below are some of the findings related to this vulnerable setting: