The Elementor Attacks: How Creative Hackers Combined Vulnerabilities to Take Over WordPress Sites

On May 6, our Threat Intelligence team was alerted to a zero-day vulnerability present in Elementor Pro, a WordPress plugin installed on approximately 1 million sites. That vulnerability was being exploited in conjunction with another vulnerability found in Ultimate Addons for Elementor, a WordPress plugin installed on approximately 110,000 sites.

We immediately released a firewall rule to protect Wordfence Premium users and contacted Elementor about the vulnerability. As this vulnerability was being actively exploited, we also publicly notified the community of the vulnerability to help protect users from being compromised.

Elementor quickly released an update for Elementor Pro the same day we published a notice on the Wordfence blog. In today’s post, we provide the technical details of the two vulnerabilities along with a closer look at how these two vulnerabilities were used together to compromise WordPress websites using these plugins.

We urge any website owners who have not yet updated to the latest versions of these plugins to do so immediately. For Elementor Pro, that is version 2.9.4 and in Ultimate Addons for Elementor, that is version 1.24.2.

Ultimate Addons for Elementor

Description: Registration Bypass
Affected Plugin: Ultimate Addons for Elementor
Plugin Slug: ultimate-elementor
Affected Versions: <= 1.24.1
CVE ID: CVE-2020-13125
CVSS Score: 7.2 (High)
CVSS Vector: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Fully Patched Version: 1.24.2

Ultimate Addons for Elementor is a plugin made by Brainstorm Force. It is an extension to Elementor, providing many additional widgets to use with Elementor.

One of the widgets adds a registration form, called the “User Registration Form” to any page. This is an easily customizable registration form that can be placed anywhere on the site. Unfortunately, there was a flaw in the functionality of this form that allowed for users to register even when registration was disabled, and even if the form widget was not actively in use on the site.

Is User Registration enabled?

The developers registered both nopriv and regular AJAX actions tied to the get_form_data function in order to provide functionality for the User Registration Form widget.

	public function __construct() {
		parent::__construct();

		add_action( 'wp_ajax_uael_register_user', array( $this, 'get_form_data' ) );
		add_action( 'wp_ajax_nopriv_uael_register_user', array( $this, 'get_form_data' ) );
		add_filter( 'wp_new_user_notification_email', array( $this, 'custom_wp_new_user_notification_email' ), 10, 3 );

Diving into the get_form_data function, we see that it was designed to retrieve the information submitted in the registration form. This data was then used to create a new user on the site using the WordPress wp_insert_user hook. Nowhere in the function did it verify that user registration was enabled on the site, nor did it do any alternative checks to verify that the registration form widget was active.

	/**
	 * Get Form Data via AJAX call.
	 *
	 * @since 1.18.0
	 * @access public
	 */
	public function get_form_data() {

		check_ajax_referer( 'uael-form-nonce', 'nonce' );

		$data     = array();
		$error    = array();
		$response = array();

		if ( isset( $_POST['data'] ) ) {

			$data = $_POST['data'];

*Note several lines are omitted for brevity.

			$user_args = apply_filters(
					'uael_register_insert_user_args',
					array(
						'user_login'      => isset( $user_login ) ? $user_login : '',
						'user_pass'       => isset( $user_pass ) ? $user_pass : '',
						'user_email'      => isset( $user_email ) ? $user_email : '',
						'first_name'      => isset( $first_name ) ? $first_name : '',
						'last_name'       => isset( $last_name ) ? $last_name : '',
						'user_registered' => gmdate( 'Y-m-d H:i:s' ),
						'role'            => isset( $user_role ) ? $user_role : '',
					)
				);

				$result = wp_insert_user( $user_args );

These missing checks were what made it possible for attackers to bypass the user registration settings on a WordPress site. Fortunately, Brainstorm Force added checks in the latest version to verify both that user registration is enabled and that the widget is active.

	/**
	 * Get Form Data via AJAX call.
	 *
	 * @since 1.18.0
	 * @access public
	 */
	public function get_form_data() {

		check_ajax_referer( 'uael-form-nonce', 'nonce' );

		$data             = array();
		$error            = array();
		$response         = array();
		$allow_register   = get_option( 'users_can_register' );
		$is_widget_active = UAEL_Helper::is_widget_active( 'RegistrationForm' );

		if ( isset( $_POST['data'] ) && '1' === $allow_register && true === $is_widget_active ) {

			$data = $_POST['data'];

Registration Nonce is always displayed

Though nonces are primarily used to mitigate CSRF attacks and verify the legitimacy of a request, they can also act as a fail-safe in instances like this where a function contains a small flaw, that is, if the nonce is undiscoverable by an attacker.

The get_form_data function did use nonce verification that could have potentially stopped rogue user registration. However, we discovered that the form_nonce was always visible in the source code of a page where a UA for Elementor widget was enabled, even when there was no form present on the page.

/**
	 * Setup Actions Filters.
	 *
	 * @since 0.0.1
	 */
	private function setup_actions_filters() {

		add_shortcode( 'uael-template', array( $this, 'uael_template_shortcode' ) );

		add_action( 'elementor/init', array( $this, 'elementor_init' ) );

		add_action( 'elementor/elements/categories_registered', array( $this, 'widget_category' ) );

		add_action( 'elementor/frontend/after_register_scripts', array( $this, 'register_widget_scripts' ) );

*Note several lines are omitted for brevity.


			wp_localize_script(
			'jquery',
			'uaelRegistration',
			array(
				'invalid_mail'       => __( 'Enter valid Email!', 'uael' ),
				'pass_unmatch'       => __( 'The specified password do not match!', 'uael' ),
				'required'           => __( 'This Field is required!', 'uael' ),
				'form_nonce'         => wp_create_nonce( 'uael-form-nonce' ),
				'incorrect_password' => __( 'Error: The Password you have entered is incorrect.', 'uael' ),
				'invalid_username'   => __( 'Unknown username. Check again or try your email address.', 'uael' ),
				'invalid_email'      => __( 'Unknown email address. Check again or try your username.', 'uael' ),
			)
		);
	}

This meant that attackers just needed to scrape the source code of pages on a site running this plugin for var uaelRegistration. If that site had at least one widget in use on any page, they would be granted a usable nonce to register on the site.

A look at the discoverable UAE nonce in the source code of a page with a UAE widget enabled.

The Exploit

Combined, these flaws made it possible for attackers to register as a subscriber on any vulnerable site and potentially use that access to pivot and exploit vulnerabilities that required subscriber level access. This is precisely what we saw being exploited in the case of the Elementor Pro vulnerability.

Special thanks to Ramuel Gall for his research contributions on this vulnerability.

Elementor Pro

Description: Authenticated Arbitrary File Upload
Affected Plugin: Elementor Pro
Plugin Slug: elementor-pro
Affected Versions: <= 2.9.3
CVE ID: CVE-2020-13126
CVSS Score: 9.9 (Critical)
CVSS Vector: CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Fully Patched Version: 2.9.4

Elementor is a popular WordPress page builder plugin, currently installed on over 5 million WordPress sites. The Pro version adds additional enhancements like the ability to upload custom icons and fonts. It also adds over 50 additional widgets to improve the page building process along with enhanced customizability.

When a plugin introduces the ability to upload files, regardless of the file type, the proper control measures should always be included in order to prevent unauthorized users from uploading files or bypassing any file filters or privileges established on the site.

Unfortunately, the ‘Custom Icon’ upload functionality in Elementor Pro did not have appropriate measures in place. Attackers discovered an effective way to bypass restrictions on which file types could be uploaded, and once authenticated they were able to upload PHP files like webshells and backdoors.

Lack of Permissions Check

Elementor Pro registers an AJAX endpoint used to trigger the icon upload function. Neither the AJAX action nor the upload function had any permission checks, allowing any authenticated user, including those with minimal permissions, the ability to trigger the function and upload a .zip file.

	public function register_ajax_actions( Ajax $ajax ) {
		$ajax->register_ajax_action( 'pro_assets_manager_custom_icon_upload', [ $this, 'custom_icons_upload_handler' ] );

Fortunately, the patched version of the plugin implemented a permissions check on the custom_icons_upload_handler function that blocks low-level users from being able to upload files.

public function custom_icons_upload_handler( $data ) {
		if ( ! current_user_can( Icons_Manager::CAPABILITY ) ) {
			return new \WP_Error( Exceptions::FORBIDDEN, 'Access denied.' );
		}

Unchecked Uploads

It appears that the custom icon upload functionality was intended to only allow .zip files that were created from the Fontello, IcoMoon, or Fontastic icon creation sites. It did this by checking the files included in the .zip file and verifying that those aligned with the files generated from the trusted icon sources. However, the plugin never checks to verify if any additional files have been included in those .zip files.

	$supported_icon_sets = self::get_supported_icon_sets();
		foreach ( $supported_icon_sets as $key => $handler ) {
			/**
			 * @var IconSets\Icon_Set_Base $icon_set_handler
			 */
			$icon_set_handler = new $handler( $results['directory'] );

			if ( ! $icon_set_handler ) {
				continue;
			}
			if ( ! $icon_set_handler->is_valid() ) {
				continue;

This made it possible for attackers to include malicious files in a trusted .zip file, bypassing the restrictions the plugin had in place. This included .php files.

Discoverable Nonce

There was a nonce check on the function, but just like we saw in the Ultimate Addons for Elementor plugin, the nonce was easily discoverable due to being included in var elementorCommonConfig as “ajax” in the page source of all administrative dashboard pages.

A closer look at the Elementor Pro nonce discovered in the page source of /wp-admin.

An attacker could find a usable nonce by scraping the page source of the /wp-admin dashboard while authenticated. This could then be used as a legitimate Elementor AJAX nonce to execute the vulnerable action and upload crafted .zip files.

The Exploit

These three flaws made it possible for attackers to upload arbitrary files by creating a Fontello, IcoMoon, or Fontastic icon .zip file, extracting that file, injecting arbitrary files of their choice to the folder, re-compressing the .zip file and uploading it to the site via the AJAX action.

Uploaded .zip files are extracted into the /wp-content/upload/elementor/custom-icon directory in a newly generated folder. If the site owner previously had not uploaded any custom icons, the malicious files would be located in the /-1 folder. If there were previously uploaded custom-icon files that directory may have been a different number such as /-5, and the attacker would need to use brute force in order to find the directory into which their malicious payload had been extracted.

An attacker would be able access any newly uploaded files, like a webshell, by going directly to the file:

https://example.com/wp-content/wp-content/upload/elementor/custom-icon/-1/backdoor.php

If the file uploaded was a .php file, the code would also be executed upon access. This allowed for remote code execution (RCE) and ultimately the total compromise of the WordPress site and hosting environment.

Disclosure Timeline for Elementor Pro

May 5, 2020 – Wordfence notified of recently patched vulnerability in Ultimate Addons for Elementor. In the same notice, it was mentioned that Elementor Pro may have a “bug” that caused rogue files present in the /custom-icons directory. We begin to investigate Elementor Pro to determine if there is a security flaw present. We conclude that there is an authenticated arbitrary file upload vulnerability present.
May 5, 2020 – We quickly provision Premium customers with a firewall rule to provide protection against the vulnerability found in Elementor Pro.
May 5, 2020 – We contact the team at Elementor to alert them of the vulnerability’s presence.
May 5, 2020 – Hosting company provides us with log files that confirm this is being actively exploited.
May 6, 2020 – We publish a public service announcement with limited details to inform users how to secure their site until a patch is available.
May 6, 2020 – Elementor releases a patch for Elementor Pro.
June 4, 2020 – Wordfence free users receive firewall rule.

Attack Scenario Walkthrough

Conclusion

In today’s post, we provided technical details of the vulnerability found in the Elementor Pro plugin and how this vulnerability was used in active exploits in conjunction with the vulnerability found in Ultimate Addons for Elementor. These flaws have both been patched and we recommend that users update to the latest versions available immediately.

Sites running Wordfence Premium have been protected from attacks against the vulnerability in Elementor Pro since May 5, 2020. Sites running the free version of Wordfence will receive the firewall rule update on June 4, 2020. If you know a friend or colleague who is running one, or both, of these plugins on their site, we highly recommend forwarding this to them immediately to help them secure their site.

Special thank you to the team at Elementor for working quickly to get a patch out to protect Elementor Pro users. Also, thank you again to Ramuel Gall, Kathy Zant, Gerroald Barron, and Stephen Rees-Carter from the Wordfence team for their assistance in researching this attack and testing mitigations.

Did you enjoy this post? Share it!

Comments

4 Comments
  • I know this may sound naive, but why do people attack sites? I never understood this. What purpose does this serve to ruin work and businesses and cause so much trouble and extra work?

    • Hi Richard,

      There are various reasons why people hack sites and it is usually due to some personal gain, like money. Hackers can earn money though the use of spam and injections on a compromised site and they can collect money or sensitive data by infecting innocent users browsing to a compromised site. Though, sometimes hackers just do it for fun because they can.

      Here is one of our blog posts that provides a little more insight as to why hackers do what they do:
      https://www.wordfence.com/blog/2016/04/hackers-compromised-wordpress-sites/

      Hopefully that helps provide you with a little more insight as to why hackers do what they do.

  • Another great and informative post. It's fascinating to see how attackers exploit these vulnerabilities, and it really makes me think about the potential pitfalls in whatever techniques I am using to build a site.

    Thanks, Chloe and Defiant team!

  • Great explanatory video, it makes the danger much more clear and “hacking” less abstract a concept. Thank you Wordfence team for protecting WP users!