miscfreehard

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/
async_import_triggerescape_sequence_dot_bypassexec_callback_injectionobject_graph_traversalfileloader_globals_leak

$ 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:

  1. No literal dots in raw input
  2. No "import" substring in raw input (blocks from x import y)
  3. No "match" substring in raw input (blocks pattern matching)
  4. Builtins cleared — no getattr, eval, exec, open, etc.
  5. Only set_builtin(key, val) available to write back into builtins
  6. 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)
  • match patterns (blocked)

Dead Ends Explored

...

$ grep --similar

Similar writeups