Page cover image

Watermelon

BlackHat MEA Qualification CTF 2024

Recently, I participated in the Blackhat MEA 2024 Qualification CTF, where I tackled a web challenge named “Watermelon.” This challenge was relatively straightforward but enjoyable. Here’s a step-by-step breakdown of how I solved it.

Challenge Overview

The challenge provided a file named app.py, which sets up the challenge API for managing users and their uploaded files. It includes basic security checks for file access and an admin-specific endpoint.

Given file

User Registration

Upon reviewing the app.py file, I discovered the user registration process:

@app.post("/register")
def register():
    if not request.json or not "username" in request.json or not "password" in request.json:
        return jsonify({"Error": "Please fill all fields"}), 400
    
    username = request.json['username']
    password = request.json['password']

    if User.query.filter_by(username=username).first():
        return jsonify({"Error": "Username already exists"}), 409

    new_user = User(username=username, password=password)
    db.session.add(new_user)
    db.session.commit()

    return jsonify({"Message": "User registered successfully"}), 201

Here, I noted that I needed to make a POST request containing a username and password to register a user account. Following this, I registered my own user account successfully.

Request and Response for user registration

User Login

After registration, I could log into the user account, which involved a process similar to registration:

@app.post("/login")
def login():
    if not request.json or not "username" in request.json or not "password" in request.json:
        return jsonify({"Error": "Please fill all fields"}), 400
    
    username = request.json['username']
    password = request.json['password']

    user = User.query.filter_by(username=username, password=password).first()
    if not user:
        return jsonify({"Error": "Invalid username or password"}), 401
    
    session['user_id'] = user.id
    session['username'] = user.username
    return jsonify({"Message": "Login successful"}), 200

With the user account registered, I was able to log in successfully.

Request for user login
Login confirmation
User profile

File Upload

Next, I explored the file upload functionality, which was also detailed in the app.py file:

@app.route("/upload", methods=["POST"])
@login_required
def upload_file():
    if 'file' not in request.files:
        return jsonify({"Error": "No file part"}), 400
    
    file = request.files['file']
    if file.filename == '':
        return jsonify({"Error": "No selected file"}), 400
    
    user_id = session.get('user_id')
    if file:
        blocked = ["proc", "self", "environ", "env"]
        filename = file.filename

        if filename in blocked:
            return jsonify({"Error":"Why?"})

        user_dir = os.path.join(app.config['UPLOAD_FOLDER'], str(user_id))
        os.makedirs(user_dir, exist_ok=True)

        file_path = os.path.join(user_dir, filename)

        file.save(f"{user_dir}/{secure_filename(filename)}")
        
        new_file = File(filename=secure_filename(filename), filepath=file_path, user_id=user_id)
        db.session.add(new_file)
        db.session.commit()
        
        return jsonify({"Message": "File uploaded successfully", "file_path": file_path}), 201

    return jsonify({"Error": "File upload failed"}), 500

I successfully uploaded several files and was able to retrieve their file paths. However, I couldn’t find the flag at this stage. This led me to consider performing LFI by manipulating the filename.

Exploiting LFI

I renamed one of my uploaded files to ../../app.py and uploaded it. This worked, allowing me to read the app.py file hosted on the challenge server. Within this file, I discovered the credentials for the admin user.

Successful file upload
Uploaded file details
Admin credentials found via LFI

Accessing the Admin Account

Using the found credentials, I logged into the admin account. Once inside, I navigated to the /admin directory and successfully located the flag.

Request for login into the admin account
Flag found

So, that was all about that challenge. See you in some other blog. PEACE!

Last updated