24 Apr 2025

WordPress Plugin Developer Security Advisory: StellarWP

One of the little understood realities of security issues with WordPress plugins is that the insecurity of them is not evenly spread across those plugins. Instead, many developers are properly securing their plugins and others get them properly secured when alerted they haven’t done that. A smaller number of plugin developers either are unable or unwilling to properly secure their plugins. With the latter group, among the issues we have seen, are developers who have introduced new serious vulnerabilities that are substantially similar to vulnerabilities that they know have been exploited in their plugins.

In situations where we become aware of developers who have shown that inability or unwillingness to properly secure their plugin, we are releasing advisories to warn customers of our service and the wider WordPress community of the risk of utilizing those developers’ plugins. In addition to checking those posts on our website for information on those advisory, we provide access to the information in several other forms. That includes through the companion plugin for our service, even when not using the service, as well as through a web browser extension and through separate data accessible from our website.

The latest addition to our advisories involves a developer who has a terrible track record with security despite being the developer of one of the most popular security plugins for WordPress. The popularity of the security plugin has declined pretty significantly despite claiming that the plugin “[r]educe[s] your WordPress website’s risk to nearly zero.” That might have something to do with the fact that the plugin doesn’t actually offer the claimed security. That suggests either the developer isn’t honest or lacks a basic understanding of security. One of those probably explains the poor security across their plugins.

The developer is StellarWP, which is a combination of several previously independent plugin developers. StellarWP is in turn part of Liquid Web, which is owned by a private equity company. That could allow the company to spend the money to get security right or it could lead to cutting corners. It has been the latter of those for years.

Kadence Blocks Vulnerability Keeps Not Getting Fixed

In August 2023, we posted about StellarWP having failed to detect a serious vulnerability in the Kadence Blocks plugins for five months and mentioned past bad security handling coming from the company:

The developer of that plugin, StellarWP, has had a terrible security track record despite developing one of the most popular security plugins. Including failing to fix a vulnerability that their security plugin was warning about and failing to implement basic security in another plugin, leading to a zero-day. That makes the issue with Kadence Blocks not all that surprising.

In May of last year, we warned our customers that they had failed in an attempt to fix a different vulnerability in the plugin. We tried to work with them to address that, but we found that they were directing reporting vulnerabilities away from themselves to a company trying to profit off of vulnerabilities. That was despite them having a page about responsible disclosure that says “it is a standard practice in security research to responsibly and privately disclose discovered vulnerabilities to the software vendor prior to public release. This is even more critical when we work together to protect users in an open source space such as the WordPress community.” We offered to help them fix the vulnerability once they stopped doing that. They didn’t take us up on that. Instead they appear to have made over ten additional attempts to fix the vulnerability without accomplishing that.

Continued Usage of Insecure maybe_unserialize() Function

Last year another security provider, Wordfence, got press coverage for themselves in a situation where they ignored that a vulnerability in a plugin from StellarWP was caused in part by a long known security issue in WordPress. That security issue is with the WordPress function maybe_unserialize(). WordPress’ response so far has not been to address that, but to suggest that plugins shouldn’t use it. StellarWP (and Wordfence) could have used their voice in the WordPress community to push for fixing that or they could have stopped using it in their plugins to avoid something known to be insecure. That hasn’t happened. That is, while having more instances of the type of vulnerability allowed by that insecurity being found and getting press coverage.

The original situation highlighted another common issue, developers not providing a clear method for contacting them about security issues. Wordfence reported not even having gotten a response from StellarWP weeks after notifying them of the vulnerability with what seemed to be the wrong contact method. There is a standard that they could support and promote other developers to help to address that, but they haven’t.

Violating Security Related WordPress Plugin Guideline With Their Security Plugin

The 13th guideline for the WordPress Plugin Directory requires that plugins use the version of third-party libraries included with WordPress instead of including their own “[f]or security and stability reasons.” We are in the process of adding detection of those libraries and warning about plugins that include those libraries to our Plugin Security Scorecard. That led to us noticing that StellarWP’s security plugin, Solid Security, is using a rather out of date version of the jQuery Form library, which is already included in WordPress. In the file /core/modules/core/js/mc-validate.js, they have version 3.51.0 of that. That was superseded in Februrary 2017. The version included in WordPress is up to date.

Recent Plugin Missing Basic Security

One attempted defense of StellarWP’s poor handling of security might be that they have inherited plugins that were insecure before they became part of the unit. While that isn’t a good defense in our view, it wasn’t hard to find that a recent plugin is insecure. And notably, it is insecure because of code that is branded as being from StellarWP itself. That plugin being Solid Performance, which was released in August.

In the file /vendor/vendor-prefixed/stellarwp/telemetry/src/Telemetry/Opt_In/Opt_In_Subscriber.php, the plugin registers a function to run during admin_init, which makes it accessible to even those not logged in to WordPress:

40
add_action( 'admin_init', [ $this, 'set_optin_status' ] );

The code is for opting in a website to telemetry, which should only be able to be done by an Administrator, but the code lacks a capability check to limit access:

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public function set_optin_status() {
 
	// We're not attempting an action.
	if ( empty( $_POST['_wpnonce'] ) ) {
		return;
	}
 
	$nonce = sanitize_text_field( $_POST['_wpnonce'] );
 
	if ( ! wp_verify_nonce( $nonce, 'stellarwp-telemetry' ) ) {
		return;
	}
 
	// We're not attempting a telemetry action.
	if ( isset( $_POST['action'] ) && 'stellarwp-telemetry' !== $_POST['action'] ) {
		return;
	}
 
	// The user did not respond to the opt-in modal.
	if ( ! isset( $_POST['optin-agreed'] ) ) {
		return;
	}
 
	$stellar_slug = Config::get_stellar_slug();
 
	if ( isset( $_POST['stellar_slug'] ) ) {
		$stellar_slug = sanitize_text_field( $_POST['stellar_slug'] );
	}
 
	$opt_in_text = '';
 
	if ( isset( $_POST['opt_in_text'] ) ) {
		$opt_in_text = sanitize_text_field( $_POST['opt_in_text'] );
	}
 
	// User agreed to opt-in to Telemetry.
	if ( 'true' === $_POST['optin-agreed'] ) {
		$this->opt_in( $stellar_slug, $opt_in_text );
	}

The code does include a nonce check, but that isn’t supposed to be used for doing a capability check as the WordPress documentation notes:

Nonces should never be relied on for authentication, authorization, or access control. Protect your functions using current_user_can(), and always assume nonces can be compromised.

That should have been caught during a security review of the plugin before it was released. Yet 8 months later it still hasn’t been addressed.

That isn’t a one-off accident, as the same issue exists in another area that should have been caught by a security review, AJAX accessible functions. In the file /vendor/vendor-prefixed/stellarwp/telemetry/src/Telemetry/Exit_Interview/Exit_Interview_Subscriber.php, the function ajax_exit_interview() is registered to be accessible to anyone logged in to WordPress:

43
add_action( 'wp_ajax_' . self::AJAX_ACTION, [ $this, 'ajax_exit_interview' ] );

It is meant to be accessed when uninstalling the plugin, so again, only Administrators should have access, but there is no capability check:

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public function ajax_exit_interview() {
	$uninstall_reason_id = filter_input( INPUT_POST, 'uninstall_reason_id', FILTER_SANITIZE_SPECIAL_CHARS );
	$uninstall_reason_id = ! empty( $uninstall_reason_id ) ? $uninstall_reason_id : false;
	if ( ! $uninstall_reason_id ) {
		wp_send_json_error( 'No reason id provided' );
	}
 
	$uninstall_reason = filter_input( INPUT_POST, 'uninstall_reason', FILTER_SANITIZE_SPECIAL_CHARS );
	$uninstall_reason = ! empty( $uninstall_reason ) ? $uninstall_reason : false;
	if ( ! $uninstall_reason ) {
		wp_send_json_error( 'No reason provided' );
	}
 
	$plugin_slug = filter_input( INPUT_POST, 'plugin_slug', FILTER_SANITIZE_SPECIAL_CHARS );
 
	$comment = filter_input( INPUT_POST, 'comment', FILTER_SANITIZE_SPECIAL_CHARS );
	$comment = ! empty( $comment ) ? $comment : '';
 
	$nonce = filter_input( INPUT_POST, 'nonce', FILTER_SANITIZE_SPECIAL_CHARS );
	$nonce = ! empty( $nonce ) ? $nonce : '';
 
	if ( ! wp_verify_nonce( $nonce, self::AJAX_ACTION ) ) {
		wp_send_json_error( 'Invalid nonce' );
	}
 
	$telemetry = $this->container->get( Telemetry::class );
	$telemetry->send_uninstall( $plugin_slug, $uninstall_reason_id, $uninstall_reason, $comment );

Avoid StellarWP’s Plugins

Those examples provide clear evidence of a fundamental continuing problem with StellarWP’s handling of security. They have shown no willingness to address the problem and in our own experience been hostile to attempts to help even fully fix issues. That they are a major security provider, which makes the whole thing even worse.

We would recommend avoiding their plugins, unless they can show that they have made significant changes to their handling of security.

22 Apr 2025

WordPress Plugin Security Review: Popup Builder

For our 47th security review of a WordPress plugin based on the voting of our customers, we reviewed the plugin Popup Builder.

If you are not yet a customer of the service, once you sign up for the service as a paying customer, you can start suggesting and voting on plugins to get security reviews. For those already using the service that haven’t already suggested and voted for plugins to receive a review, you can start doing that here. You can use our tool for doing limited automated security checks of plugins to see if plugins you are using have possible issues that would make them good candidates to get a review. You can also order a review of a plugin separately from our service.

The review was done on version 4.3.7 of Popup Builder. We checked for the following issues during it as part of our standard review:

  • Insecure file upload handling (this is the cause of the most exploited type of vulnerability, arbitrary file upload)
  • Deserialization of untrusted data
  • Security issues with functions accessible through WordPress’ AJAX functionality (those have and continued to be a common source of disclosed vulnerabilities)
  • Security issues with functions accessible through WordPress’ REST API (those have started to be a source of disclosed vulnerabilities)
  • Persistent cross-site scripting (XSS) vulnerabilities in the frontend portions of the plugin and in the admin portions accessible to users with the Author role or below
  • Cross-site request forgery (CSRF) vulnerabilities in the admin portion of the plugin
  • SQL injection vulnerabilities (the code that handles requests to the database)
  • Reflected cross-site scripting (XSS) vulnerabilities
  • Security issues with functions accessible through any of the plugin’s shortcodes
  • Security issues with functions accessible through any of the plugin’s blocks
  • Security issues with functions accessible through the admin_action action
  • Security issues with functions accessible through the admin_init action
  • Security issues with functions accessible through the admin_post action
  • Security issues with import/export functionality
  • Security issues with usage of the is_admin() function
  • Security issues with usage of the add_option(), delete_option(), and update_option() functions
  • Security issues with usage of the update_user_meta() and wp_update_user() functions
  • Security with usage of determine_current_user filter
  • Security issues with usage of the wp_set_current_user(), wp_set_auth_cookie() and wc_set_customer_auth_cookie() functions
  • Security issues with usage of the reset_password() and wp_set_password() functions
  • Security issues with usage of the extract() function
  • Lack of IP address validation
  • Proper usage of sanitize_callback when using register_setting() to register settings
  • Existence of register_uninstall_hook or uninstall.php file that removes any WordPress options and database tables added by the plugin
  • CSV injection
  • Host header injection vulnerabilities
  • Lack of protection against unintended direct access of PHP files
  • Insecure and unwarranted requests to third-party websites
  • Any additional possible issues identified by our Plugin Security Checker

Results

We found several minor vulnerabilities, as well as places where the security could be improved.

We notified the developer of the results on March 8, offered them free help to address the issues, and informed them of our disclosure policy. We haven’t heard back from them. They did release a new version several days after we contacted them, but it didn’t address the issue. Under our disclosure policy, we should have disclosed the results a week ago, but we gave them another week due to the holiday. So we are now disclosing the results.

Cross-Site Request Forgery (CSRF) Vulnerability

In the file /public/views/settings.php, the following code lacks a nonce check to prevent cross-site request forgery (CSRF):

30
31
32
33
34
35
36
37
if( isset( $_GET['hide_warning'] ) && $_GET['hide_warning'] == true )
{
	update_option('sgpb-hide_disable-custom-js-warning', 1);
	echo '<style>.notice_sgpb.sgpb_dsiable_notice{ display: none !important;}</style>';
}
if( isset( $_GET['hide_injection'] ) && $_GET['hide_injection'] == true )
{
	update_option('sgpb-hide_disable-injection-warning', 1);

Missing Capability Checks

In the file /com/classes/Updates.php, the function sgpbActivateLicense() lacks a capability check for this code:

17
18
19
20
if (isset($_POST['sgpb-license-key-'.$key])) {
	$this->sgpbVerifyNonceLicense();				
	$this->sanitizeLicense(sanitize_key($_POST['sgpb-license-key-'.$key]));
}

There is a missing capability check in the functions dismissNotification(), removeNotification(), and reactivateNotification() in the file /com/classes/NotificationCenter.php. That is also true in the function sgpbDeactivateFeedback() in the file /com/classes/Feedback.php.

Security Issues in Library

The plugin is using an outdated version of the jQuery Validation library that contains security issues according to the developer:

Upgrading that library would resolve that. The library is located at /public/js/Validate.js.

Lack of Duplication Restriction

The function popupSaveAsNew() in the file /com/classes/Actions.php doesn’t restrict what can be duplicated to only content from the plugin.

 Server-Side Request Forgery (SSRF).

The function getImageDataFromUrl() in /com/helpers/AdminHelper.php and the function checkSameOrigin() in the file /com/classes/Ajax.php use the function wp_remote_get() with an arbitrary URL. The wp_safe_remote_get() should probably be used there to prevent the possibility of server-side request forgery (SSRF).

Nonce Leakage

The plugin included a bunch of content from the plugin, including nonces, on all admin pages of the websites for all users. So, for example, someone logged in as a subscriber is getting multiple nonces even though they have access to nothing from the plugin. It would be much better to only load things on pages that they are needed on and only for relevant users.

Improper Sanitization

The function getPopupId() in the file /com/classes/ConditionCreator.php is setting the value of the GET input “post” to a variable. It looks like the value should be limited to an integer, but is sanitized as text instead.

In the file /com/classes/Actions.php, in the function getSubscribersCsvFile() the plugin checks if the value of $_GET[‘order’] is allowed after running it through sanitize_text_field(), but then use it with it run through sanitize_text_field() and wp_unslash(). That could potentially cause something not intended to be used in a SQL statement. Though, we didn’t see a way that can be done. The code would be more secure by using the relevant WordPress security function sanitize_sql_orderby() on that portion of the SQL statement.

maybe_unserialize()

The plugin is using the maybe_unserialize() function, which is known to be insecure and a Core Committer of WordPress said plugins shouldn’t be using it.

CSV Injection

When exporting subscribers to a CSV file, there isn’t protection against CSV injection.

Improper wp_ajax_nopriv Registration

The function sgpbSubsciptionFormSubmittedAction() is made AJAX accessible to those not logged in to WordPress using wp_ajax_nopriv registration, but the function itself limits access to those logged in, so it seems making it available to those not logged in is wrong.

Directory Access Restriction Issue

The plugin limits access to the files in the directory /wp-content/uploads/subscribersimportsgpb/ with a .htaccess file. That doesn’t have any effect with web servers that don’t utilize .htaccess files, including NGINX. Including a notice when that directory exists and an incompatible server is being used, telling the webmaster they should implement protection compatible with the server would improve security.

Tracking Without Consent

Guideline 7 of the WordPress Plugin Directory states that “In the interest of protecting user privacy, plugins may not contact external servers without explicit and authorized consent.” The function updateNotificationsArray() in the file /com/classes/NotificationCenter.php is making a request to the developer’s website without checking for consent.

Incomplete Uninstall

When the plugin is deleted, the code that runs in the function uninstall() in the file /com/helpers/AdminHelper.php is failing to remove all the options set by the plugin.

Lack of Protection Against Direct Access to PHP Files

Some of the plugin’s .php files do not have code at the beginning of the files to restrict direct access to them. We didn’t see anything that could be exploited in the files without the restriction in place, but adding the code the plugin already has in other files would make things more secure.

Possible Access to Unfiltered HTML to Users with Access

One final issue that we didn’t report to the developer, but should be noted, is that the plugin, by default, allows users given access to the plugin’s admin functionality to add JavaScript code to popups. By default only Administrators have access, but if users with a role below Editor were given access, they would have access to doing the equivalent of cross-site scripting (XSS) without the unfiltered_html capability. The ability to include JavaScript code can be disabled in the plugin’s settings. It would probably be better to rely on whether users have the unfiltered_html capability to decide if they have access to that functionality.

21 Apr 2025

It Doesn’t Look Like WordPress Has Had Proper Security Review Since at Least 2009

We focus on the security of WordPress plugins, so we haven’t ventured much in to the security of the core WordPress software. You would reasonably expect that others have. But what we found in just a glancing check at things suggests that either a proper security review of the software hasn’t happened since at least the end of 2009 or the issues identified were not addressed.

Missing Security Hardening

Recently it was announced that WordPress was going to be paring down the number of new major releases. That seems to be caused by a combination of only Automattic seeming all that interested in the block editor (Gutenberg) and Automattic’s reduced involvement in WordPress. (Their reduced role could be because of their poor financial state or because the head of it (who is also the head of WordPress) is still trying to blackmail a competitor.)

We have been thinking about how what is happening could impact security. Counterintuitively, it could, to an extent, lead to better security. As a lack of other new features being added could be partially filled with security improvements. For example, years late, WordPress 6.8 finally introduced more secure password hashing.

Looking at open requests to make changes to WordPress that mention the term security show various proposed security improvements that haven’t been applied to the code base. Looking through some of those suggests that security isn’t getting much focus, and that proposed security changes are not being fully applied.

As an example of the former, one proposed change involving security hardening is connected to a ticket opened in August 2021. Another involves a ticket opened in March 2023.

As an example of the latter, which adds in another wrinkle, a code change was proposed to add sanitation when user input is set to a variable. Here is how the code looks now:

591
592
$action   = $_POST['action'];
$taxonomy = get_taxonomy( substr( $action, 4 ) );

The proposed change is to use sanitize_text_field() to sanitize the user input:

591
$action   = isset( $_POST['action'] ) ? sanitize_text_field( $_POST['action'] ) : '';

Using sanitize_key() would seem to be more appropriate. That is used for fairly similar code two other places in the file, including:

1185
1186
$taxonomy        = sanitize_key( $_POST['tax'] );
$taxonomy_object = get_taxonomy( $taxonomy );

What we also noticed is that there is another similar line that still would lack sanitation in the suggested change was applied:

1091
1092
$taxonomy        = ! empty( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : 'post_tag';
$taxonomy_object = get_taxonomy( $taxonomy );

Whenever you are applying a security change, you want to make sure to check if there are other places that need to be corrected.

The Wrong Sanitization Since 2009?

Separate from checking on what is going with security of the core WordPress software, while working on code for a plugin that would edit user account info, we noticed the WordPress function to edit a user edit_user() in the file /wp-admin/includes/user.php uses the sanitize_text_field() function to sanitize the email address:

80
81
82
if ( isset( $_POST['email'] ) ) {
	$user-&gt;user_email = sanitize_text_field( wp_unslash( $_POST['email'] ) );
}

Why the code is using that instead of what would seem be the relevant sanitzation function sanitize_email() is a mystery to us. No one responded with an answer why that might be when we queried Bluesky.

That sanitization was introduced in version 2.9, which was released in December 2009. sanitize_email() was introduced in version 1.5.

Commented Out CSRF Protection Since 2011

Getting back to our checking on what is going on with security of the core WordPress software because of the slowdown, we ran across more code that appears improperly secured. In the file /wp-admin/includes/ajax-actions.php the function wp_ajax_dismiss_wp_pointer() has a nonce check to prevent cross-site request forgery (CSRF), but it is commented out without a comment explaining why:

2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
function wp_ajax_dismiss_wp_pointer() {
	$pointer = $_POST['pointer'];
 
	if ( sanitize_key( $pointer ) !== $pointer ) {
		wp_die( 0 );
	}
 
	//  check_ajax_referer( 'dismiss-pointer_' . $pointer );
 
	$dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
 
	if ( in_array( $pointer, $dismissed, true ) ) {
		wp_die( 0 );
	}
 
	$dismissed[] = $pointer;
	$dismissed   = implode( ',', $dismissed );
 
	update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
	wp_die( 1 );
}

It seems like there should be a nonce check in that code.

The comment above the function says “since 3.1.0,” but we found that the code looks to have been introduced in 3.3. That was released in December 2011.

Code Introduced Last Year Looks Insecure

In the file /wp-includes/class-wp-plugin-dependencies.php, the function heck_plugin_dependencies_during_ajax() is described in a comment above as “Checks plugin dependencies after a plugin is installed via AJAX.” You would expect there to be a capability check, as it AJAX registered to anyone logged in to WordPress. While there is some code in that only runs if the requestor has a specified capability. The rest runs no matter who requests it, assuming they provide the required nonce:

446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
public static function check_plugin_dependencies_during_ajax() {
	check_ajax_referer( 'updates' );
 
	if ( empty( $_POST['slug'] ) ) {
		wp_send_json_error(
			array(
				'slug'         => '',
				'pluginName'   => '',
				'errorCode'    => 'no_plugin_specified',
				'errorMessage' => __( 'No plugin specified.' ),
			)
		);
	}
 
	$slug   = sanitize_key( wp_unslash( $_POST['slug'] ) );
	$status = array( 'slug' => $slug );
 
	self::get_plugins();
	self::get_plugin_dirnames();
 
	if ( ! isset( self::$plugin_dirnames[ $slug ] ) ) {
		$status['errorCode']    = 'plugin_not_installed';
		$status['errorMessage'] = __( 'The plugin is not installed.' );
		wp_send_json_error( $status );
	}
 
	$plugin_file          = self::$plugin_dirnames[ $slug ];
	$status['pluginName'] = self::$plugins[ $plugin_file ]['Name'];
	$status['plugin']     = $plugin_file;
 
	if ( current_user_can( 'activate_plugin', $plugin_file ) && is_plugin_inactive( $plugin_file ) ) {
		$status['activateUrl'] = add_query_arg(
			array(
				'_wpnonce' => wp_create_nonce( 'activate-plugin_' . $plugin_file ),
				'action'   => 'activate',
				'plugin'   => $plugin_file,
			),
			is_multisite() ? network_admin_url( 'plugins.php' ) : admin_url( 'plugins.php' )
		);
	}
 
	if ( is_multisite() && current_user_can( 'manage_network_plugins' ) ) {
		$status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
	}
 
	self::initialize();
	$dependencies = self::get_dependencies( $plugin_file );
	if ( empty( $dependencies ) ) {
		$status['message'] = __( 'The plugin has no required plugins.' );
		wp_send_json_success( $status );
	}
 
	require_once ABSPATH . 'wp-admin/includes/plugin.php';
 
	$inactive_dependencies = array();
	foreach ( $dependencies as $dependency ) {
		if ( false === self::$plugin_dirnames[ $dependency ] || is_plugin_inactive( self::$plugin_dirnames[ $dependency ] ) ) {
			$inactive_dependencies[] = $dependency;
		}
	}
 
	if ( ! empty( $inactive_dependencies ) ) {
		$inactive_dependency_names = array_map(
			function ( $dependency ) {
				if ( isset( self::$dependency_api_data[ $dependency ]['Name'] ) ) {
					$inactive_dependency_name = self::$dependency_api_data[ $dependency ]['Name'];
				} else {
					$inactive_dependency_name = $dependency;
				}
				return $inactive_dependency_name;
			},
			$inactive_dependencies
		);
 
		$status['errorCode']    = 'inactive_dependencies';
		$status['errorMessage'] = sprintf(
			/* translators: %s: A list of inactive dependency plugin names. */
			__( 'The following plugins must be activated first: %s.' ),
			implode( ', ', $inactive_dependency_names )
		);
		$status['errorData'] = array_combine( $inactive_dependencies, $inactive_dependency_names );
 
		wp_send_json_error( $status );
	}
 
	$status['message'] = __( 'All required plugins are installed and activated.' );
	wp_send_json_success( $status );
}

That is commented as having existed “since 6.5.0,” which appears accurate. That version was released in April of last year.

Security Team Needed

Part of what probably helps to explain the poor security situation is WordPress’ lack of a dedicated security team. As best we can tell, what gets referred to as the WordPress Security Team is actually a group of people in the WordPress Core Team. They don’t appear to have much professional experience handling security and the leader of the team appears to not care much about proper security.

Putting in place a dedicated security team staffed with people with security experience that is properly governed is long overdue. Having that team then work with outside providers like ourselves that actually want to improve WordPress security (as opposed to many security providers profiting off of leaving things insecure), unlike the plugin team, could make quick work of a lot of unnecessary insecurity.

In the meantime, if someone wants us do a security review of WordPress in the context of what we do with security reviews of plugins. The price would be $6100 USD.

4 Apr 2025

Hacker Probing for WordPress Plugin That Wordfence Exposed Critical Vulnerability in Without Making Sure Fix Is Available

Yesterday, we had what would appear to be a hacker probing for usage of the WordPress plugin Checkout Mestres WP on our website by requesting the readme.txt file for it like this:

/wp-content/plugins/checkout-mestres-wp/readme.txt

What would explain their interest in that plugin? Well, the plugin was closed on March 27, which could suggest that there is a vulnerability in it:

On March 31, the developer submitted an update for the plugin, which involved them adding a capability check to limit access to some code to only users with the manage_options capability. That capability is normally only accessible to Administrators. Someone knowledgeable with the handling of security of WordPress plugins, looking at the code changed, would understand the significance of that. They would also understand that the code is still vulnerable, though less vulnerable.

For those not knowledgeable, they were told by Wordfence three days before how this could be weaponized:

The Checkout Mestres do WP for WooCommerce plugin for WordPress is vulnerable to unauthorized modification of data that can lead to privilege escalation due to a missing capability check on the cwmpUpdateOptions() function in versions 8.6.5 to 8.7.5. This makes it possible for unauthenticated attackers to update arbitrary options on the WordPress site. This can be leveraged to update the default role for registration to administrator and enable user registration for attackers to gain administrative user access to a vulnerable site.

While it can be debated if disclosing that information was right to do, what can’t really be debated is that Wordfence should have worked with the team running the WordPress Plugin Directory to make sure a fix was available before that type of disclosure. Instead, the 600 or so websites using the plugin have been exposed to be exploitable. That isn’t bad for Wordfence, since they could sell those websites their $600 a year hack cleanup service.

We have offered for years to do most of the work needed to make sure situations like this don’t happen, but the team running the plugin directory has steadfastly refused to accept our help or take it on by themselves. Wordfence has been notably silent about all of that, which makes sense when you understand their business model. They are not alone in that, as other security providers, including one owned by Automattic, WPScan, have given further exposure to that information about the vulnerability without insisting that it get a fix.

How much work would be required to have avoided this situation? Almost none.

The simplest option here would be to make the chance the developer already made on the 31st and provide that as an update for those already using plugin. The first part of that has happened, the latter hasn’t happened. That is, even though the team running the plugin directory can provide the update without making the plugin available to others. It is a capability they have had for at least a decade, but strangely, don’t take advantage of. As can be confirmed with the previous version installed on a WordPress website:

Plugin Actually Got More Insecure

In August 2021, the Matt Mullenweg owned WP Tavern had a post where a Wordfence employee was quoted, claiming that WordPress plugins were getting more secure and that most vulnerabilities being found were old:

Both Wordfence and WPScan claim that the greater number of vulnerabilities reported this year is indicative of the growth of the WordPress ecosystem and a maturing, healthy interest in security. Themes and plugins aren’t getting more insecure over time but rather there are more people interested in discovering and reporting vulnerabilities.

“First and foremost, we aren’t seeing a lot of newly introduced vulnerabilities in plugins and themes but rather we are seeing a lot of older vulnerabilities in older plugins and themes being reported/fixed that just weren’t detected until now,” Wordfence Threat Analyst Chloe Chamberland said.

“Vulnerabilities aren’t being introduced as frequently and more vulnerabilities are being detected simply due to the higher activity of researchers which is in turn positively impacting the security of the WordPress ecosystem. Considering it isn’t newly introduced vulnerabilities that are being frequently discovered, I feel confident in saying that the increase in discoveries doesn’t indicate that the ecosystem is getting less secure at all but rather getting more secure.”

Chamberland also said she believes there is a domino effect when vulnerabilities are disclosed to vendors and they learn from their accidents, causing them to develop more secure products in the future.

Contrary to that claim (and actual evidence at the time), this vulnerability was only introduced in to the plugin in September. How insecure the code is also contradicts that vendors learn from “accidents” that other developers have.

The Vulnerable Code

The attempt to fix the vulnerability was made in the function cwmpUpdateOptions() in the file /backend/core/base/ajax.php. In that file, the function is made accessible through WordPress’ AJAX functionality to those logged in to WordPress:

29
add_action( 'wp_ajax_cwmpUpdateOptions', 'cwmpUpdateOptions' );

And to those not logged in:

30
add_action( 'wp_ajax_nopriv_cwmpUpdateOptions', 'cwmpUpdateOptions' );

Here is how the function looked before the change:

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function cwmpUpdateOptions() {
    if (isset($_POST['data'])) {
        parse_str($_POST['data'], $parsed_data);
        $campos_com_html = array('cwmp_remember_password_body', 'parcelas_mwp_payment_second_pre', 'parcelas_mwp_payment_list_format_s_juros', 'parcelas_mwp_payment_list_format_c_juros');
        foreach ($parsed_data as $campo => $valor) {
            if (in_array($campo, $campos_com_html)) {
                $safe_value = $valor;
            } else {
                $safe_value = sanitize_text_field($valor);
            }
            update_option(sanitize_text_field($campo), $safe_value);
        }
        echo "Opções atualizadas com sucesso.";
    } else {
        echo "Nenhum dado foi enviado.";
    }
 
    wp_die();
}

The function allows updating arbitrary WordPress options (settings). You don’t have to be a security expert to know that someone not logged in to WordPress shouldn’t be able to do that.

The change made was to limit access to the functionality to Administrators:

31
32
33
34
function cwmpUpdateOptions() {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( 'Acesso negado.', 403 );
    }

The code still is lacking protection against cross-site request forgery (CSRF), so there is still a vulnerability.

Code that allows updating arbitrary options really shouldn’t exist in a plugin like this. Instead, only intended options should be updateable.

More Vulnerabilities Probably Exist

Based on a quick check of the code it looks like there are more vulnerabilities in the code.

Free Warning

As the vulnerability is being targeted by a hacker, we are adding accurate data on it to the free data that comes with our Plugin Vulnerabilities plugin.

3 Apr 2025

6 WordPress Plugins With a Million or More Installs Still Using JavaScript Library That Was EOL’d at End of 2023

As we continue to expand the ability for our Plugin Security Scorecard to detect third-party libraries included with WordPress plugins, we continue to find that popular plugins are not handling their usage of those well. While preparing to notify a plugin developer that they were using a known insecure version of a library, we noticed another library in the plugin that we hadn’t yet added to the tool. That library being Vue.js. Version 2 of that reached end of life at the end of 2023. That means if there were a vulnerability or lesser security issue, then an update wouldn’t be released. (There is a scammy security provider claiming to provide further updates for it.)

While working on adding detection for the library, we found that 6 plugins with a million or more installs still contain version 2 of the library. All but one of them are not even using the latest version of version 2. That plugin is using the latest is CookieYes, which has a million installs and contains 2.7.16.

Awesome Motive, which has a chief security officer who is the Security Reviewer on the WordPress Plugin Review Team, has three of the plugins out of the six. The 4+ million install WP Mail SMTP and 1+ million install OptinMonster include version 2.7.14.. As the next version was release in October 2023, they haven’t updated the library in the last 17 months. Their other plugin, the 1+ million install Smash Balloon Instagram Feed, includes version 2.6.12. The next release after that was in June 2021, so they are nearing 4 years without updating it.

The final two plugins both are using 2.6.11, which was superseded in August 2020. The plugins also both have 1+ million installs. The plugins are ElementsKit Lite and Regenerate Thumbnails.

A security plugin from a major plugin developer, WPMU DEV, also is including version 2 of the library. The Defender security plugin contains version 2.7.8. The next release came out in August 2022.

27 Jan 2025

Patchstacks’s Vulnerability Disclosure Program (VDP) Goes Against Important Requirements of EU’s Cyber Resilience Act

In October, the European Union approved the Cyber Resilience Act (CRA) after two years of development. It seems well thought out, probably best shown with the authors understanding the harm that security providers’ own bad security often creates. Unfortunately, in the run up to passage, WordPress and others had tried to portray it as having negative effects on open source software (while also providing a misleading portrayal of security handling at WordPress). That is, despite it actually requiring for-profits entities taking advantage of open source software being required to help secure it. Also, unfortunately, an unscrupulous WordPress security provider is trying to mislead the WordPress community as what the act entails to continue harmfully directing vulnerability reporting away from developers.

That provider being Patchstack, here is how it mentions complying with the CRA through them on a page on its website about their vulnerability disclosure program (VDP):

Comply with the European Cyber Resilience Act Starting Q4 in 2024, The Cyber Resilience Act (CRA) will introduce obligatory software support and vulnerability disclosure guidelines for all commercial software with users in the European Union. Read more CRA REQUIREMENTS Set up a Vulnerability Disclosure Policy (VDP) Share data with EU vulnerability database Notify users about vulnerability exploits Provide security updates (separately) Patchstack helps with patch validation

While they claim “Starting Q4 in 2024, The Cyber Resilience Act (CRA) will introduce obligatory software support and vulnerability disclosure guidelines,” in truth, the act becomes applicable years later. Specially, it becomes applicable to plugin developers on “11 December 2027, with exception of the reporting obligations concerning actively exploited vulnerabilities and severe incidents having an impact on the security of products with digital elements, which should apply from 11 September 2026.” That continues Patchstack’s long track record of misleading to outright dishonest claims.

Things get worse from there. Patchstack’s whole intent is to get vulnerabilities reports directed away from plugin developers to themselves, so they can profit off of the vulnerabilities. That also prevents legitimate security providers from properly vetting vulnerability claims. That makes it harder to catch when vulnerabilities haven’t actually been fixed, which happens often. It also makes it harder to catch misleading claims. That doesn’t seem like something the EU would be supportive of, and the act goes directly against that. Here is the relevant section of the introduction, with our emphasis added:

Manufacturers of products with digital elements should put in place coordinated vulnerability disclosure policies to facilitate the reporting of vulnerabilities by individuals or entities either directly to the manufacturer or indirectly, and where requested anonymously, via CSIRTs designated as coordinators for the purposes of coordinated vulnerability disclosure in accordance with Article 12(1) of Directive (EU) 2022/2555. Manufacturers’ coordinated vulnerability disclosure policy should specify a structured process through which vulnerabilities are reported to a manufacturer in a manner allowing the manufacturer to diagnose and remedy such vulnerabilities before detailed vulnerability information is disclosed to third parties or to the public. Moreover, manufacturers should also consider publishing their security policies in machine-readable format. Given the fact that information about exploitable vulnerabilities in widely used products with digital elements can be sold at high prices on the black market, manufacturers of such products should be able to use programmes, as part of their coordinated vulnerability disclosure policies, to incentivise the reporting of vulnerabilities by ensuring that individuals or entities receive recognition and compensation for their efforts. This refers to so-called ‘bug bounty programmes’.

So you need to be able to report issues directly to the developer, which isn’t what happens with Patchstack’s program. More importantly, it states that the process should happen before “detailed vulnerability information is disclosed to third parties.” Patchstack is a third-party trying to profit off of that information.

Then in Article 13, that is reiterated (emphasis ours):

17. For the purposes of this Regulation, manufacturers shall designate a single point of contact to enable users to communicate directly and rapidly with them, including in order to facilitate reporting on vulnerabilities of the product with digital elements.

In line with CRA, our Plugin Security Scorecard lowers the grade of plugins that are directing vulnerabilities away from developers like Patchstack is attempting to get them to do.

While they have a check mark next to the “Share data with EU vulnerability database” on the page, the relevant European Union database doesn’t yet exist. So they couldn’t be sharing with it.

Two other components don’t make sense as they are “Notify users about vulnerability exploits” and “Provide security updates (separately).” Those would be things that a developer would need to do.

It also is important to note the CRA has other requirements that Patchstack doesn’t mention and what they are offering to get vulnerabilities reported to them doesn’t offer. One notable element is having a software bill of materials (SBOM) to help make sure known vulnerable libraries are not in software:

In order to facilitate vulnerability analysis, manufacturers should identify and document components contained in the products with digital elements, including by drawing up an SBOM. An SBOM can provide those who manufacture, purchase, and operate software with information that enhances their understanding of the supply chain, which has multiple benefits, in particular it helps manufacturers and users to track known newly emerged vulnerabilities and cybersecurity risks. It is of particular importance that manufacturers ensure that their products with digital elements do not contain vulnerable components developed by third parties. Manufacturers should not be obliged to make the SBOM public.

As we noted last week, a plugin whose developer claims Patchstack is ensuring is secure contains two libraries with known vulnerabilities, one on which has been publicly known to be vulnerable for 21 months. Our Plugin Scorecard checks if a plugin has a listed SBOM.

The CRA also requires that an assessment is done of the security of software:

In order to ensure that products with digital elements are secure both at the time of their placing on the market as well as during the time the product with digital elements is expected to be in use, it is necessary to lay down essential cybersecurity requirements for vulnerability handling and essential cybersecurity requirements relating to the properties of products with digital elements. While manufacturers should comply with all essential cybersecurity requirements related to vulnerability handling throughout the support period, they should determine which other essential cybersecurity requirements related to the product properties are relevant for the type of product with digital elements concerned. For that purpose, manufacturers should undertake an assessment of the cybersecurity risks associated with a product with digital elements to identify relevant risks and relevant essential cybersecurity requirements in order to make available their products with digital elements without known exploitable vulnerabilities that might have an impact on the security of those products and to appropriately apply suitable harmonised standards, common specifications or European or international standards.

As we will cover in a follow up post, Patchstack is admitting to not properly vetting vulnerability reports, so they are not even close to doing these assessments. Our Plugin Scorecard checks if a plugin is listing the URL of a security review done of the plugin.

Patchstack Promotes being Funded by European Union

While Patchstack is trying to use the CRA to mislead developers and creating additional security risk for the WordPress community, they are claiming on their website to be funded by the European Union:

It appears that the precursor to Patchstack, WebARX, had received funding back in 2021.

It doesn’t seem like the EU would be happy to be associated with a company trying to profit off o misleading the public about their legislation.

6 Jun 2025

Plugin Vulnerabilities Customers Helped Make WordPress Plugins More Secure, Week of June 6

Our customers provide us with the ability to help make WordPress plugins more secure. Mostly, with plugins they use, but to a lesser extent other plugins. That work often goes unmentioned. So we are highlighting that to help to better understand what is going on and how signing up for our service can help to expand that work.

Vulnerable Library Updated

Someone checked the plugin WP File Manager, which has 1+ million installs, through our Plugin Security Scorecard and it flagged usage of an outdated and insecure third-party library. We then notified the developer of that and they have now released a new version of the plugin to address that. You can check plugins you use through that to see if they are using known insecure libraries. [Read more]

30 May 2025

Patchstack Now Withholding Misappropriated Information Needed to Secure Plugins in WordPress Plugin Directory From WordPress

Last week, we posted how WordPress had left a known vulnerable WordPress plugin with 100,000+ installs that is being targeted by a hacker in the WordPress Plugin Directory. The plugin continues to be in the plugin directory despite one of the Team Reps for the Plugins Team, David Perez, and the Senior Team member of the team, Samuel (Otto) Wood, being informed of that.

It turns out that there is another party partially responsible for the situation. It is a party that has already been engaged in unethical behavior and things have gotten worse now. [Read more]

30 May 2025

Plugin Vulnerabilities Customers Helped Make WordPress Plugins More Secure, Week of May 30

Our customers provide us with the ability to help make WordPress plugins more secure. Mostly, with plugins they use, but to a lesser extent other plugins. That work often goes unmentioned. So we are highlighting that to help to better understand what is going on and how signing up for our service can help to expand that work.

Vulnerability That Went Unfixed for 9 Months in 2+ Million Install Plugin Fixed

Last week, we checked on an attempt to fix a vulnerability in the 2+ million install MC4WP: Mailchimp for WordPress and found the developer had incorrectly fixed the instance of the issue they attempted to fix. And they had failed to fix another instance entirely. That had happened 9 months ago. Unfortunately, other WordPress security providers who claim to have security experts that check over vulnerability claims either didn’t vet this or missed both of those issues. We checked on that attempted fix because at least one of our customers started using the plugin. We reached out to the developer and this week they fixed the issue. [Read more]

23 May 2025

WordPress Plugin Submission Review Seems to Have Failed Badly With ConvertPro

Earlier this week the team running the WordPress Plugin Directory were touting how great things are going. They proclaimed that the “WordPress Ecosystem is Growing,” basing that on “plugin submissions hav[ing] doubled in 2025.” They didn’t mention how much usage those plugins have, which might have something to do with the pretty bleak numbers. A recently introduced plugin to the directory highlights that there are other problems that the team seems to be blind to with what they are responsible for.

Fake Install Count?

We are in the process of reviewing WordPress plugins used by our customers to see if they contain any third-party libraries we still need to add detection to for to our Plugin Security Scorecard. That led us to coming across the plugin ConvertPro. Or more accurately, one instance of it. The WordPress Plugin Directory listing for it seems rather odd. The plugin is at version 1.0.0 and has no reviews, yet it has 20,000+ installs: [Read more]

23 May 2025

Plugin Vulnerabilities Customers Helped Make WordPress Plugins More Secure, Week of May 23

Our customers provide us with the ability to help make WordPress plugins more secure. Mostly, with plugins they use, but to a lesser extent other plugins. That work often goes unmentioned. So we are highlighting that to help to better understand what is going on and how signing up for our service can help to expand that work.

Missing Capabilities Check Addressed

Based on our proactive monitoring flagging an issue in an update of the BEAF plugin, which has 20,000+ installs, the developer addressed a lacked of a capabilities check that could have allowed an attacker to change plugin settings and upload files. All plugins being used by our customer go through an extended version of that monitoring on a weekly basis. [Read more]

23 May 2025

Long Overdue Security Review of WordPress Would Cost Only 0.25% of WP Engine’s Estimate of Cost of One WordPress Website

This week WordPress managed host WP Engine released “[i]nsights from a global study of 1,700+ digital leaders on the real costs of maintaining” websites. The study suggests, not surprisingly, that there is a lot of concern when it comes to security with usage of WordPress. We will have more on that a in separate post, but one figure included in their study highlighted how little it would cost to improve the security of WordPress.

WP Engine tabbed the total cost of one WordPress website at $2,408,789: [Read more]

22 May 2025

WordPress Hasn’t Addressed Hacker Targeted Plugin With 100,000+ Installs That Has Unfixed “Critical” Vulnerability

Yesterday, data we track showed that what was likely a hacker was probing for usage of the 100,000+ install WordPress plugin TI WooCommerce Wishlist, by requesting its readme.txt file. Why would a hacker be interested in the plugin? Presumably there shouldn’t be any publicly known unfixed vulnerabilities, as the plugin hasn’t been closed in the WordPress plugin directory:

[Read more]

21 May 2025

Is Brizy, Patchstack, or Both to Blame For Lack of Fix for Vulnerable WordPress Plugin With 80,000+ Installs After 8 Months?

While looking into an issue with the Brizy WordPress plugin, which has 80,000+ installs, we ran across a concerning security situation. The most recent support topic for the plugin is titled “Is this plugin abandoned?” and reads:

4 weeks without any update, Wordfence show a critical vulnerability and has not been made compatible with the latest WordPress 6.8 [Read more]