20 Jul 2023

Wordfence Falsely Claims It Has to Rely on Inaccurate Plugin Vulnerability Data from Patchstack

On an unfortunately too regular basis, we are finding that vulnerabilities that were supposed to be fixed in plugins being used by our customers haven’t been fully fixed and in some cases haven’t been fixed at all. That is the case with a vulnerability that was recently supposed to have been fixed in the 200,000+ install plugin Ultimate Member. In looking into that, we ran across several other problems involving competing data providers that are not being honest about their data and its sourcing.

In our recent monitoring of possible discussions about plugin vulnerabilities in the WordPress Support Forum, we have seen a Wordfence employee claiming that Wordfence doesn’t have control over their own plugin vulnerability data. Here was one instance of that:

The developer will have to release a new version and verify the vulnerability is no longer there with Patchstack, who are the entity that verified it. There’s no known timescale for this that I can see, but if it is rectified, Wordfence will update accordingly.

And here was another:

Our reporting of this seems to be down to a CVE ID being issued, and the vulnerability being verified on Patchstack. Wordfence will report these, even though we weren’t the entity that decided it was a valid vulnerability.

It’s best to patch the vulnerability and inform Patchstack as Wordfence will, in turn, also update when a fix is confirmed.

While Patchstack claims to verify vulnerabilities, real world results show that isn’t happening. (We recently worked with the developer of another plugins now used by one of our customer to fix a vulnerability that Patchstack had incorrectly claimed had been fixed five months before.) So Wordfence having to rely on them would be a very bad idea. It doesn’t even make sense that they would have to do that. How would it be they can’t control their own information? It also turns out it isn’t true.

Recently, Patchstack vaguely claimed that Ultimate Member had contained a cross-site request forgery (CSRF) vulnerability. The “details” for that are as follows:

Nguyen Xuan Chien discovered and reported this Cross Site Request Forgery (CSRF) vulnerability in WordPress Ultimate Member Plugin. This could allow a malicious actor to force higher privileged users to execute unwanted actions under their current authentication. This vulnerability has been fixed in version 2.6.1.

Here is Wordfence’s entry for what is claimed to be the same issue based on a matching CVE ID:

The Ultimate Member plugin for WordPress is vulnerable to Cross-Site Request Forgery in versions up to, and including, 2.6.0. This is due to missing or incorrect nonce validation on the duplicate_form function. This makes it possible for unauthenticated attackers to duplicate forms created with the plugin via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.

That information can’t have come from Patchstack, since it refers to a specific issue and Patchstack has just vaguely described what a cross-site request forgery (CSRF) vulnerability is. So clearly Wordfence doesn’t have to rely on claims made by Patchstack and can actually do their own checking, but for whatever reason they publicly stating they can’t do that. Considering how often Wordfence isn’t being honest about things, it would be notable if they managed to tell the truth.

There is a larger problem than Wordfence not telling the truth about the control of their own data here. As described by Patchstack, the vulnerability still exists in the plugin. What Wordfence described had been resolved, but they managed to miss that issue is part of a larger issue that remains unfixed.

One CSRF Issue Down

The issue that Wordfence is referencing was in the file /includes/admin/class-admin.php and involved the function duplicate_form. That function is registered to be accessible through functionality of the plugin:

78
add_action( 'um_admin_do_action__duplicate_form', array( &$this, 'duplicate_form' ) );

That function was modified to add a nonce check to prevent CSRF in version 2.6.1. Here is the start of the function in the previous version:

1748
1749
1750
1751
1752
1753
function duplicate_form( $action ) {
	if ( ! is_admin() || ! current_user_can('manage_options') ) {
		die();
	}
	if ( ! isset( $_REQUEST['post_id'] ) || ! is_numeric( $_REQUEST['post_id'] ) ) {
		die();

And here is the new version:

1749
1750
1751
1752
1753
1754
1755
public function duplicate_form( $action ) {
	if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
		die();
	}
 
	if ( empty( $_REQUEST['post_id'] ) || empty( $_REQUEST['nonce'] ) || ! wp_verify_nonce( $_REQUEST['nonce'], "um-duplicate_form{$_REQUEST['post_id']}" ) ) {
		die();

Before the nonce check is a check to make sure the request is coming from a user with the manage_options capability. So only Administrators are intended to access that.

Through CSRF, an attacker could cause an Administrator to take that action without intending it.

If anyone had bothered to check the file further, they would have seen that there were more places where that nonce check is missing.

More CSRF Issues to Go

That function is one of 11 that is registered to be accessed in the same way in the file:

71
72
73
74
75
76
77
78
79
80
81
82
add_action( 'um_admin_do_action__user_cache', array( &$this, 'user_cache' ) );
add_action( 'um_admin_do_action__user_status_cache', array( &$this, 'user_status_cache' ) );
add_action( 'um_admin_do_action__purge_temp', array( &$this, 'purge_temp' ) );
add_action( 'um_admin_do_action__manual_upgrades_request', array( &$this, 'manual_upgrades_request' ) );
add_action( 'um_admin_do_action__duplicate_form', array( &$this, 'duplicate_form' ) );
add_action( 'um_admin_do_action__um_hide_locale_notice', array( &$this, 'um_hide_notice' ) );
add_action( 'um_admin_do_action__um_can_register_notice', array( &$this, 'um_hide_notice' ) );
add_action( 'um_admin_do_action__um_hide_exif_notice', array( &$this, 'um_hide_notice' ) );
add_action( 'um_admin_do_action__user_action', array( &$this, 'user_action' ) );
add_action( 'um_admin_do_action__check_templates_version', array( &$this, 'check_templates_version' ) );
 
add_action( 'um_admin_do_action__install_core_pages', array( &$this, 'install_core_pages' ) );

Here is the beginning on the function right before duplicate_form(), manual_upgrades_request():

1613
1614
1615
1616
1617
1618
public function manual_upgrades_request() {
	if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
		die();
	}
 
	$last_request = get_option( 'um_last_manual_upgrades_request', false );

Like the other function, it is restricted to Administrators, but it still lacks a nonce check. It isn’t the only one. It also looks like there might be ones that should have a capabilities check, but don’t.

We have notified the developer of the remaining issues and offered to help them address them.

Proof of Concept

The following proof of concept will make a manual upgrade request, when logged in as an Administrator.

Replace “[path to WordPress]” with the location of WordPress.

http://[path to WordPress]/wordpress/wp-admin/admin.php?um_adm_action=manual_upgrades_request

Plugin Security Scorecard Grade for Patchstack

Checked on March 5, 2025
D

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


Plugin Security Scorecard Grade for Ultimate Member

Checked on November 23, 2024
C+

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

Leave a Reply

Your email address will not be published. Required fields are marked *