SECURITY ADVISORY / 01

CVE-2025-6895 Exploit & Vulnerability Analysis

Complete CVE-2025-6895 security advisory with proof of concept (PoC), exploit details, and patch analysis for melapress-login-security.

melapress-login-security products NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An unauthenticated attacker who knows an arbitrary WordPress user's meta key and can predict or enumerate meta values can bypass authentication entirely and log in as that user. No nonce, no password, no session required.

GET /wp-login.php?mls_temp_login_token=attacker_controlled_value HTTP/1.1
Host: target.wordpress.local
User-Agent: Mozilla/5.0
Cookie: wordpress_logged_in=; wp-settings-time=

When the token parameter matches a user meta value stored in the database (e.g., a temporary login token previously created by an admin), the get_valid_user_based_on_token() function retrieves that user's ID and logs them in via wp_set_current_user() and wp_set_auth_cookie() without any capability checks. An attacker observes a successful 302 redirect to /wp-admin/ with an authenticated session cookie. If the target user is an administrator, the attacker gains full site control.


What the Patch Did

Before:

public static function get_valid_user_based_on_token( $token ) {
    if ( empty( $token ) ) {
        return false;
    }

    global $wpdb;
    $meta_key = 'mls_temp_user_expires_on';

    $sql = '
        SELECT  ID, display_name
        FROM        ' . $wpdb->users . ' INNER JOIN ' . $wpdb->usermeta . '
        ON ' . $wpdb->users . '.ID = ' . $wpdb->usermeta . '.user_id
        WHERE ' . $wpdb->usermeta . '.meta_key = "mls_temp_user_token"
        AND ' . $wpdb->usermeta . '.meta_value = %s
        AND ' . $wpdb->usermeta . '.user_id NOT IN (
            SELECT user_id FROM ' . $wpdb->usermeta . ' WHERE meta_key = "mls_temp_user_expires_on" AND CAST( meta_value AS UNSIGNED ) < %d
        )
    ';

    $result = $wpdb->get_row( $wpdb->prepare( $sql, $token, time() ) );

    if ( ! empty( $result ) && ! empty( $result->ID ) ) {
        wp_set_current_user( $result->ID );
        wp_set_auth_cookie( $result->ID );
        return $result;
    }

    return false;
}

After:

public static function get_valid_user_based_on_token( $token ) {
    if ( empty( $token ) ) {
        return false;
    }

    if ( ! \is_user_logged_in() ) {
        return false;
    }

    global $wpdb;
    $meta_key = 'mls_temp_user_expires_on';

    $sql = '
        SELECT  ID, display_name
        FROM        ' . $wpdb->users . ' INNER JOIN ' . $wpdb->usermeta . '
        ON ' . $wpdb->users . '.ID = ' . $wpdb->usermeta . '.user_id
        WHERE ' . $wpdb->usermeta . '.meta_key = "mls_temp_user_token"
        AND ' . $wpdb->usermeta . '.meta_value = %s
        AND ' . $wpdb->usermeta . '.user_id NOT IN (
            SELECT user_id FROM ' . $wpdb->usermeta . '.meta_key = "mls_temp_user_expires_on" AND CAST( meta_value AS UNSIGNED ) < %d
        )
    ';

    $result = $wpdb->get_row( $wpdb->prepare( $sql, $token, time() ) );

    if ( ! empty( $result ) && ! empty( $result->ID ) ) {
        wp_set_current_user( $result->ID );
        wp_set_auth_cookie( $result->ID );
        return $result;
    }

    return false;
}

The patch adds a single authorization gate: if ( ! \is_user_logged_in() ) { return false; } at the start of get_valid_user_based_on_token(). This is the WordPress is_user_logged_in() function, which checks whether the current request carries a valid, pre-existing authentication session. The function now refuses to process temporary login tokens unless the caller is already authenticated.


Root Cause

CWE-862: Missing Authorization combined with CWE-287: Improper Authentication.

The vulnerable code path flows as follows: when a user lands on the login page with the mls_temp_login_token parameter set, a hooked handler (not shown in the diff but referenced by the function signature) calls get_valid_user_based_on_token() with the attacker-controlled token from $_REQUEST['mls_temp_login_token']. That parameter enters as untrusted user input. The function performs a SQL query using $wpdb->prepare() to escape it, preventing injection, but does not verify that the requester is already logged in. It then directly calls wp_set_auth_cookie() on the queried user, elevating an anonymous request to authenticated. The trust boundary crossed is: unauthenticated requester → authenticated session, with no authorization check in between.


Why It Works

The load-bearing line is if ( ! \is_user_logged_in() ) { return false; }.

Without this check, an attacker can call the function from unauthenticated context, query the temporary-login meta table for any token value they know or guess, and immediately authenticate as that user. Removing this single line restores the vulnerability.

The surrounding code—the SQL prepare, the expiration check, the user existence validation—is defence in depth: it prevents invalid tokens from triggering a login, and it stops expired temporary-login sessions from being reused. But none of it enforces the critical rule: "only already-authenticated users may perform token-based re-authentication." That rule lives in the one line the patch adds. The other lines were already there; the engineer correctly identified that the missing piece was the authorization gate itself, not the input handling.


Hardening Checklist

  • Add is_user_logged_in() checks before re-authentication flows. Any function that calls wp_set_auth_cookie() or wp_set_current_user() must first verify the requester is already authenticated via is_user_logged_in(), unless the function is explicitly designed for first-time login (e.g., wp_authenticate() in core).

  • Audit all query handlers hooked to init or wp_loaded. If a handler processes $_REQUEST or $_GET parameters and performs privilege-level changes, enumerate every code path and confirm each one has a capability check (current_user_can()) or authentication check (is_user_logged_in()) at the entry point.

  • Use wp_verify_nonce() for all state-changing operations. The temporary-login feature should require a nonce even for authenticated users to prevent CSRF. Verify the nonce before accepting the mls_temp_login_token parameter, not after querying the database.

  • Test unauthenticated access to every authentication-related endpoint. In your test suite, confirm that calling your token-exchange function with a valid token but no prior session returns false and does not call wp_set_auth_cookie().


References

  • https://nvd.nist.gov/vuln/detail/CVE-2025-6895

Frequently asked questions about CVE-2025-6895

What is CVE-2025-6895?

CVE-2025-6895 is a security vulnerability identified in melapress-login-security. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2025-6895?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2025-6895. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2025-6895 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology affecting melapress-login-security. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2025-6895?

CVE-2025-6895 affects melapress-login-security. Check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2025-6895?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls for melapress-login-security.

What is the CVSS score for CVE-2025-6895?

The severity rating and CVSS scoring for CVE-2025-6895 affecting melapress-login-security is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.