Recently, I participated in the , 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.
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.
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.
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.
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.
So, that was all about that challenge. See you in some other blog. PEACE!