PwnMe CTF CTF Quals 2025 - ProfileEditor (WEB)

I couldn’t get the remote flag because I solved it from local after the CTF ended. However, I solved it correctly and got the local flag, so I’m sharing how to access and solve it.

The challenge provided the full source code, making it a white-box web challenge written in Python using Flask.

Analysis

There were 4 endpoints:

  • /login: Logs in with a username and password, creating a session.
  • /register: Creates an account with a username and password.
  • /profile: Allows users to view and edit their profile.
  • /show_profile: Displays the profile without allowing edits.

The most interesting endpoint was /show_profile, as it read the profile directly from a file. Since there was a flag.txt file on the server, I wanted to exploit it to read the flag using the /show_profile endpoint.

I didn’t use /profile because, even though it also reads profile files, it appends .html to the username when reading the file. So, if my username were flag.txt, it would attempt to read flag.txt.html instead. You can see this behavior in the implementation below:

1
2
3
4
5
6
7
profiles_file = 'profile/' + session.get('username')
profiles_file = profiles_file if profiles_file.endswith('.html') else profiles_file + '.html'

profile=''
if exists(profiles_file):
with open(profiles_file, 'r') as f:
profile = f.read()

Now, let’s take a look at /show_profile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@app.route('/show_profile', methods=['GET', 'POST'])
def show_profile():
if not session.get('username'):
return redirect('/login')

profiles_file = 'profile/' + session.get('username') # [1]

if commonpath((app.root_path, abspath(profiles_file))) != app.root_path:
return render_template('error.html', msg='Error processing profile file!', return_to='/profile')

profile = ''
if exists(profiles_file): # [2]
with open(profiles_file, 'r') as f:
profile = f.read()

return render_template('show_profile.html', username=session.get('username'), profile=profile)

Since the file it reads depends on the username, if the file exists, you can read it. However, as seen in [1], profiles_file is formatted as profile/username, meaning you need to use ../ to climb up directories. Since flag.txt is not in the profile directory. This is known as Path Traversal.

Solution Summary

  1. Register and Log in with the username ../flag.txt.
  2. Access /show_profile, and it will display the contens of flag.txt.