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?
Automatically mitigate vulnerabilities in real-time without changing code.
See pricingIdentify vulnerabilities in your plugins and get recommendations for fixes.
Request auditProtect your users, improve server health and earn additional revenue.
Patchstack for hostsAbout 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
🤝 You can help us make the Internet a safer place
Streamline your disclosure process to fix vulnerabilities faster and comply with CRA.
Get started for freeProtect your users too! Improve server health and earn added revenue with proactive security.
Patchstack for hostsReport vulnerabilities to our gamified bug bounty program to earn monthly cash rewards.
Learn more