Challenge: Secure Secretpickle · CTF: GPN CTF 2026 · Category: Web · Difficulty: Medium
Summary
The hardened sibling of secretpickle: the server now pickle.loads() attacker data
inside a write-only seccomp sandbox, killing both server RCE and the handler-hook
trick. But the adminbot action lets you choose the URL the headless Chromium visits
with no scheme check, and it returns a full-page screenshot — so file:///flag.txt
renders the flag straight into the image.
Solution
Step 1: Why the easy path is dead
secretpickle.py is unchanged: requests are pickle blobs obfuscated by a hardcoded
prefix + XOR key, posted to POST /{b64}. The only diff from the easy version is the
server now decodes with a sandboxed loader:
| |
safe_loader.py runs pickle.loads in a subprocess under seccomp with only
write allowed, default KILL. A forged os.system pickle still returns HTTP 200,
but the reduce is SIGSYS-killed, so you just get an error dict back. The subprocess
can’t read the flag or RCE the parent (its only output is JSON on stdout), so the easy
solve (RCE → monkeypatch action_handler → log the adminbot’s plaintext login) is gone.
DOMPurify is 3.4.7 (default config, no usable bypass), so client-side XSS is out too.
Step 2: Abuse the adminbot as a file-read primitive
The flag is the adminbot’s admin password (/flag.txt). adminbot.py runs Chromium
with --no-sandbox as the same user that reads /flag.txt, and the adminbot action
hands our URL straight to page.goto() with no validation, then returns the screenshot:
| |
Point it at file:///flag.txt and read the flag out of the returned PNG:
| |
The bot may need a warm start (first call can return adminbot failed: Remote end closed connection); just retry. The flag appears as monospace text in flag.png.
Flag
| |