build-a-builtin-revenge
b01lersc
Task: a Python 3.14 pyjail forbids literal dots, 'import' and 'match' substrings, wipes builtins, and provides only set_builtin helper. Solution: use escape sequences to bypass filters, trigger __import__ via async coroutine creation, inject exec callback to evaluate dotted code constructed with \\x2e escapes, then traverse object graph to FileLoader globals for os module access.
$ ls tags/ techniques/
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
build-a-builtin-revenge — b01lers CTF 2026
Description
No separate organizer prompt was included; the challenge was distributed through the service source and Dockerfile.
This is the "revenge" version of build-a-builtin. The original challenge allowed dotless attribute access via from x import y syntax. The revenge version specifically blocks this by adding an "import" substring filter, creating what initially appears to be a perfect catch-22.
Challenge Summary
#!/usr/local/bin/python3 import builtins code = input("code > ") if "." in code: print("Nuh uh") exit(1) if "import" in code or "match" in code: print("Slopadoodledoo") exit(1) def set_builtin(key, val): builtins.__dict__[key] = val exec = exec builtins.__dict__.clear() exec(code, {"set_builtin": set_builtin}, {})
Key constraints:
- No literal dots in raw input
- No "import" substring in raw input (blocks
from x import y) - No "match" substring in raw input (blocks pattern matching)
- Builtins cleared — no
getattr,eval,exec,open, etc. - Only
set_builtin(key, val)available to write back into builtins - Flag path randomized as
/flag-<32hex>.txt
The critical observation: exec = exec saves the real exec function into the module globals BEFORE clearing builtins. This exec is accessible via set_builtin.__globals__["exec"].
Analysis
The Catch-22
We need exec to evaluate code containing dots. But exec is trapped in set_builtin.__globals__, and accessing .__globals__ requires:
- Dots (blocked)
from x import y(blocked — contains "import")getattr()(cleared)matchpatterns (blocked)
Dead Ends Explored
...
$ grep --similar
Similar writeups
- [misc][free]bctf-infra— b01lersc
- [misc][Pro]Ergastulum— 0xl4ugh
- [misc][Pro]__pyjail__— kalmarctf
- [misc][free]rustjail— b01lersc
- [misc][Pro]Locked Away— hackthebox