Account Takeover Vulnerability Patched in Password Policy Manager Plugin

Published 16 July 2025
Table of Contents

This blog post is about an unauthenticated account takeover vulnerability in the Password Policy Manager plugin. If you're a Password Policy Manager user, please update the plugin to the version 2.0.5.

✌️ Our users are protected from this vulnerability. Are yours?

Web developers

Automatically mitigate vulnerabilities in real-time without changing code.

See pricing
Plugin developers

Identify vulnerabilities in your plugins and get recommendations for fixes.

Request audit
Hosting companies

Protect your users, improve server health and earn additional revenue.

Patchstack for hosts

About Password Policy Manager plugin

The plugin Password Policy Manager, which has over 5,000 active installations, as per their official plugin description, allows site owners to create and enforce strong and secure password policies with features like force password change, reset the password, password security, strong password, user password manager, password strength, auto password expiry, etc.

The security vulnerability

In the version 2.0.4 and below, the plugin is vulnerable to an account takeover vulnerability, which allows any subscriber+ attackers to takeover any user of the WordPress site. The vulnerability has been patched in the version 2.0.5 and is tracked with CVE-2025-31019.

The root cause of the issue lies in the moppm_pass2login_redirect function:

// TRIMMED CODE

add_action( 'init', array( $this, 'moppm_pass2login_redirect' ) );

// TRIMMED CODE

public function moppm_pass2login_redirect(){
	$nonce = isset( $_POST['moppm_login_nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['moppm_login_nonce'] ) ) : null;
	if ( ! wp_verify_nonce( $nonce, 'moppm-login-nonce' ) ) {
		return;
	}
	$user_id = isset( $_POST['mopppm_userid'] ) ? sanitize_text_field( wp_unslash( $_POST['mopppm_userid'] ) ) : '';
	$currentuser = get_user_by( 'id', $user_id );
	do_action( 'miniorange_post_authenticate_user_login', $currentuser, '', null );
	wp_set_current_user( $user_id, $currentuser->user_login );
	delete_expired_transients( true );
	wp_set_auth_cookie( $user_id, true );
	wp_safe_redirect( home_url());
	exit;
}

The function moppm_pass2login_redirect which is passed to the init hook, is basically taking the $nonce and $user_id value from the user. If the nonce check succeeds, it is directly setting the cookies for the provided user ID without any authentication check.

Tracing where the nonce token is being issued, we come across the function moppm_reset_pass_form which is called whenever the user is not compliant with the password policy standard enforced by the plugin.

<?php

function moppm_reset_pass_form( $user ) {
	$session_id = moppm_generate_id();
	$user_id    = $user->ID;
	set_transient( $session_id, $user_id, 90 );
	$miniorange_logo = plugins_url( 'password-policy-manager' . DIRECTORY_SEPARATOR . 'includes' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'shield.png' );
?>
// TRIMMED CODE

<input type="hidden" name="moppm_login_nonce" value="<?php echo esc_attr( wp_create_nonce( 'moppm-login-nonce' ) ); ?>"/>

// TRIMMED CODE

<?php } ?>

After grabbing the nonce from the forced password reset form displayed by the plugin, an attacker can easily use the administrator's user ID to login as the admin and gain full access of the WordPress site.

The patch

In the version 2.0.3, the vendor attempted to fix the issue by adding an additional parameter $session_id which is required to match along with the user ID to prevent takeover.

While this method patches the original vulnerability, there is a very slight chance of bypass. If an attacker can somehow create a transient with an arbitrary numeric values of their choice, it would lead to takeover. While it is not directly possible to create arbitrary transients in WP, some functionalities or features in any other plugins might enable the attack.

After getting back with the vendor regarding the incomplete patch, they fully patched the issue in the version 2.0.5. The set transient now has a user ID parameter that prevents users from manipulating and potentially taking over the account.

Conclusion

It is necessary to ensure that there is maximum caution when it comes to handling sessions. Proper authentication/authorization check should be implemented whenever user sessions are being assigned. It is also important to note that nonce should not be a replacement for authentication or authorization check against sensitive actions.

Want to learn more about finding and fixing vulnerabilities?

Explore our Academy to master the art of finding and patching vulnerabilities within the WordPress ecosystem. Dive deep into detailed guides on various vulnerability types, from discovery tactics for researchers to robust fixes for developers. Join us and contribute to our growing knowledge base.

Timeline

28 April, 2025We found the vulnerability and notified the vendor.
02 May, 2025The vendor publishes incomplete patch. We reach out the vendor with additional information.
05 June, 2025No action taken based on the information provided by us. We publish the vulnerability entry to our database marking it as incomplete patch.
10 June, 2025The vendor fully patched the issue. We marked the published entry as patched.
16 July, 2025Security advisory article publicly released.

🤝 You can help us make the Internet a safer place

Plugin developer?

Streamline your disclosure process to fix vulnerabilities faster and comply with CRA.

Get started for free
Hosting company?

Protect your users too! Improve server health and earn added revenue with proactive security.

Patchstack for hosts
Security researcher?

Report vulnerabilities to our gamified bug bounty program to earn monthly cash rewards.

Learn more

The latest in Security Advisories

Looks like your browser is blocking our support chat widget. Turn off adblockers and reload the page.
crossmenu