Challenge description

you can run anything on this! please dont hack me

Source code

blacklist = "._0x/|?*[]{}<>\"'=()\\\t "
blacklist2 = ['eval', 'exec', 'compile', 'import', 'os', 'sys', 'cat', 'ls', 'exit', 'list', 'max', 'min', 'set', 'tuple']

def validate(code):
    for char in blacklist:
        if char in str(code):
            return False
    for word in blacklist2:
        if word in str(code):
            return False
    return True

if __name__ == '__main__':
    print("------------------------------")
    print("Welcome to my very cool python interpreter! \nI hope I blacklisted enough... \nYou can never be too careful with these things...")
    print("Send an empty line to run!")
    print("------------------------------")
    safe_code = ""
    while (True):
        unsafe_code = input(">>> ")
        if (unsafe_code == ""):
            try:
                exec(safe_code)
            except:
                print("Error executing!")
            break
        unsafe_code = unsafe_code.replace("open", "")
        unsafe_code = unsafe_code.replace("print", "")
        if (not validate(unsafe_code)):
            print("Invalid code!")
            continue
        safe_code += str(unsafe_code)+ "\n"

First analysis

In this challenge we had to read the flag.txt file. The script let us upload python code trough the while loop, blacklisting a number of characters. Most notably:

  • dots and underscore (so no __builtins__)
  • any kind of parenthesis (so no functions, at least in the canonical sense, see later…)
  • and no space (wtf man, even the spaces?)

Note that open and print are not blacklisted, they just get replaced with an empty string.

Resolution

First rule don’t panic, what can we use? open and print can actually be used, we just need to send something like oopenpen that get sanitized to open so that’s good, but how do we call a function without parenthesis? Let’s introduce python decorators!

@print
@input
class A:pass

We create a class that does nothing and invoke the function input with the class name as parameter and then the function print on the result of input. This is the same as running

print(input(...))

We don’t care about the argument of the input, as it gets stringified and then used as the string printed before the prompt.

Since @ are not blacklisted we are golden. What we can do then is something similar to:

@pprintrint
@sorted
@oopenpen
@input
class A:pass

and give as input: flag.txt to print it. @sorted is necessary because open returns a file object and not the file content itself, other alternatives would have been list, next or similar.

But we need the space between class and A and there is nothing much we can do about it. We need to input a separator that is ignored by the blacklist. We have 2 options:

  • we encode the payload so that there aren’t blacklisted chars included the space
  • we use a different separator that doesn’t make our payload explode within the exec

Since exec doesn’t seem to respect different encoding even when the #coding:blabla header is defined we went for the second options. After many test the only character we found was Form feed (ASCII 0x0c)

Final Payload

@pprintrint
@sorted
@oopenpen
@input
class<\x0c>A:pass

We then proceed to use pwntools to send the payload in bytes (so that we can handle for the the special character correctly) and it’s done!