This blog post is about a premium Thrive Theme vulnerability. If you’re a premium Thrive Theme user, please update the plugin to at least version 3.24.0.
You can sign up for the Patchstack Community plan to be notified about vulnerabilities as soon as they become disclosed.
For plugin developers, we have security audit services and Threat Intelligence Feed API for hosting companies.
About the Thrive Theme
The theme Thrive (premium version), which is estimated to have over 110,000 active installations, is one of the more popular premium themes for online business purposes.
This theme is a premium builder focused on creating an online business. Thrive Theme is a fully-fledged WordPress theme and site builder, with drag and drop, front-end customization power. Thrive Theme allows us to visually create and customize blog post templates, sidebar, page layout, category pages, and more.
The security vulnerability
The Thrive theme suffers from an authenticated privilege escalation vulnerability. This issue occurs because there is a lack of access control and input validation on the Dismiss Tooltip action.
The function utilizes the update_user_meta
function, which could lead to a complete privilege escalation if a user can fully control the meta key and value saved. The described vulnerability was fixed in version 3.24.0 and assigned CVE-2023-47782.
Authenticated Privilege Escalation
The underlying vulnerability is located in the action_dismiss_tooltip
function:
/**
* Dismiss user tooltip
*
* @return array
*/
public function action_dismiss_tooltip() {
$response = [];
$user = wp_get_current_user();
/* double check, just to be sure */
if ( $user ) {
$key = $this->param( 'meta_key' );
$value = $this->param( 'meta_value' );
update_user_meta( $user->ID, $key, $value );
$response[ $key ] = $value;
}
return $response;
}
The function intends to dismiss a popup tooltip for the current user. Unfortunately, it allows users to arbitrarily set the meta key and value passed to the update_user_meta
function. This condition could lead to a privilege escalation since the user can modify their wp_capabilities meta which contains the user’s role definition.
The action_dismiss_tooltip
function itself can be called from the handle
function below which is hooked to the wp_ajax_tcb_editor_ajax
action:
/**
* Handles the ajax call
*/
public function handle() {
if ( wp_verify_nonce( $this->param( 'nonce' ), self::NONCE_KEY ) === false ) {
$this->error( __( 'This page has expired. Please reload and try again', 'thrive-cb' ), 403, 'nonce_expired' );
}
$custom = $this->param( 'custom' );
if ( empty( $custom ) || ! method_exists( $this, 'action_' . $custom ) ) {
$this->error( 'Invalid request.', 404 );
}
$action = 'action_' . $custom;
/* restore WAF-protected fields */
TCB_Utils::restore_post_waf_content();
/**
* Action triggered before any handler code is executed
* Allows setting up everything needed for the request, e.g. global objects
*
* @param TCB_Editor_Ajax $instance
*/
do_action( 'tcb_ajax_before', $this );
/**
* Action called just before the custom ajax callbacks.
*
* @param {TCB_Editor_Ajax} $this
*/
do_action( 'tcb_ajax_before_' . $custom, $this );
$response = call_user_func( array( $this, $action ) );
$response = apply_filters( 'tcb_ajax_response_' . $custom, $response, $this );
if ( $this->param( 'expect' ) === 'html' ) {
wp_die( $response ); // phpcs:ignore
}
$this->json( $response );
}
Notice that there is no permission or role check on the function and only nonce validation is implemented. We tried to search if a lower permission role user can fetch the nonce value.
We observed that one of the functions that can generate the nonce value is tcb_auth_check_data
function:
/**
* Add Architect ajax nonce to the after auth data so we can refresh it
*
* @param $data
*
* @return mixed
*/
function tcb_auth_check_data( $data ) {
$data ['tcb_nonce'] = wp_create_nonce( TCB_Editor_Ajax::NONCE_KEY );
return $data;
}
The tcb_auth_check_data
function itself is a hooked function to the tvd_auth_check_data
filter. We then check that the filter can be called from the login_footer
function:
public function login_footer() {
global $interim_login;
if ( empty( $interim_login ) || $interim_login !== 'success' || empty( $_POST ) || empty( $_POST['tvd_auth_check_user_key'] ) || empty( $this->tvd_interim_user_id ) ) {
return;
}
/**
* Problem: during the login POST, after login, the user does not seem to be actually available
*/
$user_id = $this->tvd_interim_user_id;
/**
* This is used to correctly re-generate the nonce
*/
$_COOKIE[ LOGGED_IN_COOKIE ] = $this->tvd_interim_login_cookie;
$user_key = $this->get_user_editor_key( $user_id );
wp_set_current_user( $user_id );
if ( $user_key === $_POST['tvd_auth_check_user_key'] ) {
/* pass data that we need after the login auth */
$data = apply_filters( 'tvd_auth_check_data', array(
'rest_nonce' => wp_create_nonce( 'wp_rest' ),
) );
include 'handle-login.php';
}
}
The above function is hooked to the WordPress built-in function with the same name which is login_footer
. If we look closely, the above function will check the $interim_login
variable. This indicates that the function will check if the user authenticates to the site using the interim-login
process.
In general, $interim_login
tells WordPress that the user’s session has expired, prompts the user to log back in, and takes them back to the page they were on when the session expired.
So, for example, if the user is sitting in the dashboard all day and then gets back on and tries to edit something but their session expired, and they are no longer logged in, WordPress will call interim-login
and allow the user to log back in and continue editing where they left off.
The only thing left to trigger the tvd_auth_check_data filter is to fetch the $user_key
value that is generated from the $this->get_user_editor_key
function. This value apparently can be fetched from wp-admin
the dashboard since it will be generated in the auth_enqueue_scripts
function:
public function __construct() {
/**
* Actions used for handling the interim login ( login via popup in Thrive Theme Dashboard )
*/
add_action( 'login_footer', array( $this, 'login_footer' ) );
add_action( 'set_logged_in_cookie', array( $this, 'set_logged_in_cookie' ), 10, 4 );
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'auth_enqueue_scripts' ) );
}
public static function auth_enqueue_scripts() {
tve_dash_enqueue_script( 'tvd-auth-check', TVE_DASH_URL . '/inc/auth-check/auth-check.js' );
wp_localize_script( 'tvd-auth-check', 'tvd_auth_check', array( 'userkey' => static::generate_editor_key() ) );
}
To summarize all of the codes, to fetch the nonce, an authenticated user with a Subscriber role needs to log in first and fetch the user key from the wp-admin
dashboard page source. The user then needs to log out and proceed to perform the interim-login
process with the supplied tvd_auth_check_user_key
value. The tcb_nonce
value then will be reflected in the HTTP response. From that, the user can request to the dismiss tooltip action and modify their wp_capabilities
user meta value.
Note that this vulnerability can be triggered on a default theme installation without additional required configuration.
The patch
Since the main issue is an arbitrary user meta update, limiting the meta key that will be updated should be enough to fix the main issue. The patch can be seen below:
Additionally, to prevent lower-role users from accessing the editor Ajax action, the vendor decided to add access control to the handle
function using the has_external_access
function. The patch can be seen below :
Conclusion
Always implement proper permission and nonce validation on each function that processes or views sensitive data. The usage of nonce validation only, without a proper permission check could still lead to a broken access control if the user can fetch the checked nonce value.
To utilize the update_user_meta
function, we need to implement a whitelist on which the meta key could be updated. It is also recommended to apply sanitization on the supplied meta value.
Timeline
Help us make the Internet a safer place
Making the WordPress ecosystem more secure is a team effort, and we believe that plugin developers and security researchers should work together.
- If you’re a plugin developer, join our mVDP program, making it easier to report, manage, and address vulnerabilities in your software.
- If you’re a security researcher, join Patchstack Alliance to report vulnerabilities & earn rewards.