Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2023-32243: 1+ Million Sites Affected by Critical Privilege Escalation Vulnerability in Essential Addons for Elementor Plugin

Improper Authentication vulnerability in WPDeveloper Essential Addons for Elementor allows Privilege Escalation. This issue affects Essential Addons for Elementor: from 5.4.0 through 5.7.1.

CVE
#vulnerability#js#git#wordpress#intel#php#perl#auth

This blog post is about the Essential Addons for Elementor plugin vulnerability. If you’re a Essential Addons for Elementor user, please update the plugin to at least version 5.7.2.

Patchstack Developer and Business plan users are protected from the vulnerability. You can also 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 Essential Addons for Elementor WordPress plugin

The plugin Essential Addons for Elementor (versions >= 5.4.0 and <= 5.7.1, free version), which has over 1 million active installations, is known as the most popular Elementor addons plugins in WordPress.

This WordPress plugin enhances the Elementor page building experience with 90+ creative elements and extensions. This plugin adds powers to our page builder using the easy-to-use elements those were designed to make our next WordPress page and posts design easier and prettier than ever before.

The security vulnerability in Essential Addons for Elementor

This plugin suffers from an unauthenticated privilege escalation vulnerability and allows any unauthenticated user to escalate their privilege to that of any user on the WordPress site.

It is possible to reset the password of any user as long as we know their username thus being able to reset the password of the administrator and login on their account. This vulnerability occurs because this password reset function does not validate a password reset key and instead directly changes the password of the given user. The described vulnerability was fixed in version 5.7.2 and assigned CVE-2023-32243.

Find out more from the Patchstack database

The start of discovering this vulnerability came from observing the init hook located in the register_hooks function:

// Login | Register
add_action('init', [$this, 'login_or_register_user']);

Let’s see the login_or_register_user function that will be triggered:

public function login_or_register_user() {
    do_action( 'eael/login-register/before-processing-login-register', $_POST );
    // login or register form?
    if ( isset( $_POST['eael-login-submit'] ) ) {
        $this->log_user_in();
    } else if ( isset( $_POST['eael-register-submit'] ) ) {
        $this->register_user();
    } else if ( isset( $_POST['eael-lostpassword-submit'] ) ) {
        $this->send_password_reset();
    } else if ( isset( $_POST['eael-resetpassword-submit'] ) ) {
        $this->reset_password();
    }
    do_action( 'eael/login-register/after-processing-login-register', $_POST );

}

The function will perform a couple of checks if certain $_POST parameter are set and will call the corresponding function. The underlying vulnerability is located in the reset_password function. Based on the official commit, the affected function only exists starting from version 5.4.0.

public function reset_password() {
    $ajax   = wp_doing_ajax();
    $page_id = 0;
    if ( ! empty( $_POST['page_id'] ) ) {
        $page_id = intval( $_POST['page_id'], 10 );
    } else {
        $err_msg = esc_html__( 'Page ID is missing', 'essential-addons-for-elementor-lite' );
    }

    $widget_id = 0;
    if ( ! empty( $_POST['widget_id'] ) ) {
        $widget_id = sanitize_text_field( $_POST['widget_id'] );
    } else {
        $err_msg = esc_html__( 'Widget ID is missing', 'essential-addons-for-elementor-lite' );
    }

    $rp_data = [
        'rp_key' => ! empty( $_POST['rp_key'] ) ? sanitize_text_field( $_POST['rp_key'] ) : '',
        'rp_login' => ! empty( $_POST['rp_login'] ) ? sanitize_text_field( $_POST['rp_login'] ) : '',
    ];

    update_option( 'eael_resetpassword_rp_data_' . esc_attr( $widget_id ), maybe_serialize( $rp_data ), false );

    update_option( 'eael_show_reset_password_on_form_submit_' . $widget_id, true, false );

    if (!empty( $err_msg )){
        if ( $ajax ) {
            wp_send_json_error( $err_msg );
        }
        update_option( 'eael_resetpassword_error_' . $widget_id, $err_msg, false );

        if (isset($_SERVER['HTTP_REFERER'])) {
            wp_safe_redirect($_SERVER['HTTP_REFERER']);
            exit();
        }
    }

    if ( empty( $_POST['eael-resetpassword-nonce'] ) ) {
        $err_msg = esc_html__( 'Insecure form submitted without security token', 'essential-addons-for-elementor-lite' );
        if ( $ajax ) {
            wp_send_json_error( $err_msg );
        }
        update_option( 'eael_resetpassword_error_' . $widget_id, $err_msg, false );

        if (isset($_SERVER['HTTP_REFERER'])) {
            wp_safe_redirect($_SERVER['HTTP_REFERER']);
            exit();
        }
    }
    if ( ! wp_verify_nonce( $_POST['eael-resetpassword-nonce'], 'essential-addons-elementor' ) ) {
        $err_msg = esc_html__( 'Security token did not match', 'essential-addons-for-elementor-lite' );
        if ( $ajax ) {
            wp_send_json_error( $err_msg );
        }
        update_option( 'eael_resetpassword_error_' . $widget_id, $err_msg, false );

        if (isset($_SERVER['HTTP_REFERER'])) {
            wp_safe_redirect($_SERVER['HTTP_REFERER']);
            exit();
        }
    }
    $settings = $this->lr_get_widget_settings( $page_id, $widget_id);

    if ( is_user_logged_in() ) {
        $err_msg = isset( $settings['err_loggedin'] ) ? __( Helper::eael_wp_kses( $settings['err_loggedin'] ), 'essential-addons-for-elementor-lite' ) : esc_html__( 'You are already logged in', 'essential-addons-for-elementor-lite' );
        if ( $ajax ) {
            wp_send_json_error( $err_msg );
        }
        update_option( 'eael_resetpassword_error_' . $widget_id, $err_msg, false );

        if (isset($_SERVER['HTTP_REFERER'])) {
            wp_safe_redirect($_SERVER['HTTP_REFERER']);
            exit();
        }
    }

    do_action( 'eael/login-register/before-resetpassword-email' );

    $widget_id = ! empty( $_POST['widget_id'] ) ? sanitize_text_field( $_POST['widget_id'] ) : '';

    // Check if password is one or all empty spaces.
    $errors = [];
    if ( ! empty( $_POST['eael-pass1'] ) ) {
        $post_eael_pass1 = trim( $_POST['eael-pass1'] );

        if ( empty( $post_eael_pass1 ) ) {
            $errors['password_reset_empty_space'] = isset( $settings['err_pass'] ) ? __( Helper::eael_wp_kses( $settings['err_pass'] ), 'essential-addons-for-elementor-lite' ) : esc_html__( 'The password cannot be a space or all spaces.', 'essential-addons-for-elementor-lite' );
        }
    } else {
        if ( empty( $_POST['eael-pass1'] ) ) {
            $errors['password_reset_empty_space'] = isset( $settings['err_pass'] ) ? __( Helper::eael_wp_kses( $settings['err_pass'] ), 'essential-addons-for-elementor-lite' ) : esc_html__( 'The password cannot be a space or all spaces.', 'essential-addons-for-elementor-lite' );
        }
    }

    if( ! empty( $_POST['eael-pass1'] ) && strlen( trim( $_POST['eael-pass1'] ) ) == 0 ){
        $errors['password_reset_empty'] = esc_html__( 'The password cannot be empty.', 'essential-addons-for-elementor-lite' );
    }
    
    // Check if password fields do not match.
    if ( ! empty( $_POST['eael-pass1'] ) && $_POST['eael-pass2'] !== $_POST['eael-pass1'] ) {
        $errors['password_reset_mismatch'] = isset( $settings['err_conf_pass'] ) ? __( Helper::eael_wp_kses( $settings['err_conf_pass'] ), 'essential-addons-for-elementor-lite' ) : esc_html__( 'The passwords do not match.', 'essential-addons-for-elementor-lite' );
    }

    if ( ( ! count( $errors ) ) && isset( $_POST['eael-pass1'] ) && ! empty( $_POST['eael-pass1'] ) ) {
        $rp_login = isset( $_POST['rp_login']) ? sanitize_text_field( $_POST['rp_login'] ) : '';
        $user = get_user_by( 'login', $rp_login );
        
        if( $user || ! is_wp_error( $user ) ){
            reset_password( $user, sanitize_text_field( $_POST['eael-pass1'] ) );
-----------------------------------------------------------------------------------

First, we need to set a random value in $_POST[‘page_id’] and $_POST[‘widget_id’] so the $err_msg is not set. We also need to set $_POST[‘eael-resetpassword-nonce’] since the nonce value will be verified on the code. In order to set the password, we need to supply the same password string to $_POST[‘eael-pass1’] and $_POST[‘eael-pass2’] since it will be checked.

If we already pass all of above condition, the code will construct a $rp_login variable from $_POST[‘rp_login’]. The code then will construct a $user object using the get_user_by function by searching the login (username) value that match the $rp_login variable.

If the $user object exists and there is no error, the code will directly reset the users’ password using the reset_password function.

At this point the question is perhaps how we can get our hands on the essential-addons-elementor nonce value. Turns out that this nonce value is present in the main front-end page of the WordPress site since it will be set in the $this->localize_objects variable by the load_commnon_asset function:

// localize object
$this->localize_objects = apply_filters( 'eael/localize_objects', [
    'ajaxurl'            => admin_url( 'admin-ajax.php' ),
    'nonce'              => wp_create_nonce( 'essential-addons-elementor' ),

The $this->localize_objects variable will be used as the object on the wp_localize_script call in the frontend_asset_load function:

public function frontend_asset_load() {
    $handle        = 'eael';
    $this->post_id = get_the_ID();

    $this->elements_manager->get_element_list( $this->post_id );
    $this->load_commnon_asset();
    $this->register_script();

-------------------- CUTTED HERE ------------------------------------

    wp_localize_script( $handle, 'localize', $this->localize_objects );
}

The function eventually will be called from init_hook and it will be set as a function handler of wp_enqueue_scripts hook that will display all of the enqueued scripts and styles.

protected function init_hook() {
    add_action( 'wp_footer', [ $this, 'add_inline_js' ], 100 );
    add_action( 'wp_footer', [ $this, 'add_inline_css' ], 15 );
    add_action( 'after_delete_post', [ $this, 'delete_cache_data' ] );

    add_action( 'wp_enqueue_scripts', [ $this, 'frontend_asset_load' ], 100 );
    add_action( 'elementor/frontend/before_enqueue_styles', [ $this, 'ea_before_enqueue_styles' ] );
    add_action( 'elementor/theme/register_locations', [ $this, 'load_asset_per_location' ], 20 );
    add_filter( 'elementor/files/file_name', [ $this, 'load_asset_per_file' ] );
}

Note that this vulnerability could be triggered on a default installation or configuration of the Essential Addons for Elementor plugin.

The patch in Essential Addons for Elementor

Since this vulnerability exists because the code directly resets a user password without properly checking if the password reset key is present and legitimate, the patch is pretty straight forward. The vendor decide to use the eael_resetpassword_rp_data_* value configured from eael_redirect_to_reset_password function to validate the reset password process. The patch can be seen below.

Conclusion

Keep in mind that unauthenticated users can execute the init and also admin_init hooks on WordPress, so we need to add a proper access control and nonce check to the function handler if it contains a certain action that performs something that interacts with the database.

Also pay extra attention to anything that is related to the login, registration and password reset/recovery process. We highly recommend utilizing the check_password_reset_key function to check for the reset password key.

Disclosure timeline of the vulnerability

08 May, 2023 – We found the vulnerability and reached out to the plugin vendor.
11 May, 2023 – Essential Addons for Elementor version 5.7.2 was published to patch the reported issues.
******11 May, 2023****** – Added the vulnerabilities to the Patchstack vulnerability database.
******11 May, 2023****** – Published the article.

Since we’ve detected that third-parties have had access to the vulnerability information via monitoring the changelog and have made the issue public, we’ve decided to disclose the vulnerability early.

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 that makes 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.

Related news

WordPress Elementor Lite 5.7.1 Arbitrary Password Reset

On May 11 2023, Essential Addons for Elementor, a WordPress plugin with over one million active installations, released a patch for a critical vulnerability that made it possible for any unauthenticated user to reset arbitrary user passwords, including user accounts with administrative-level access. Versions 5.7.1 and below are affected.

WordPress Plug-in Used in 1M+ Websites Patched to Close Critical Bug

The privilege escalation flaw is one in thousands that researchers have disclosed in recent years.

Severe Security Flaw Exposes Over a Million WordPress Sites to Hijack

A security vulnerability has been disclosed in the popular WordPress plugin Essential Addons for Elementor that could be potentially exploited to achieve elevated privileges on affected sites. The issue, tracked as CVE-2023-32243, has been addressed by the plugin maintainers in version 5.7.2 that was shipped on May 11, 2023. Essential Addons for Elementor has over one million active

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907