To evaluate the source code of the application and find the cause of the vulnerability, we need to find the appropriate file where vulnerability exists.

To interactively attach the shell inside docker, use the following command:

docker exec -it vuln_app /bin/bash

You may notice that your shell has changed and you are inside the container runtime at /app directory:

root@b7ae01fedd3f:/app# 

After searching the /app directory, we encountered a file named app.py whose truncated contents are as follows:

from flask import Flask, render_template, request, redirect, url_for
 
app = Flask(__name__)
 
# Dummy data - User credentials and profiles
users = {
}
 
# Dummy login credentials
login_credentials = {
}
 
# Variable to store current logged in user
current_user = None
 
@app.route('/')
def home():
    return render_template('login.html')
 
@app.route('/login', methods=['GET', 'POST'])
def login():
    global current_user
 
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
 
        if username in login_credentials and login_credentials[username] == password:
            current_user = username
            return redirect(url_for('profile', username=username))
        else:
            error = "Login failed. Invalid username or password."
            return render_template('login.html', error=error)
 
    return render_template('login.html')
 
@app.route('/profile/<username>')
def profile(username):
    global current_user
 
    if current_user is None:
        return redirect(url_for('login'))
 
    # Fetch profile based on username (vulnerable IDOR code)
    if username in users:
        profile = users[username]
        return render_template('profile.html', username=username, profile=profile)
    else:
        return "User profile not found."
 
@app.route('/logout')
def logout():
    global current_user
    current_user = None
    return redirect(url_for('home'))
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

If you remember, our vulnerability exists in /profile endpoint, so let’s focus onto the function used for the /profile endpoint.

@app.route('/profile/<username>')
def profile(username):
    global current_user
 
    if current_user is None:
        return redirect(url_for('login'))
 
    # Fetch profile based on username (vulnerable IDOR code)
    if username in users:
        profile = users[username]
        return render_template('profile.html', username=username, profile=profile)
    else:
        return "User profile not found."

Let’s now break this function for a better understanding.

The first statement in the function is global current_user, which uses a global variable reference to the currently logged in user.

The initial if condition redirects the user to login page (/login) if the user is currently logged in, which is a valid security measure for checking Authentication.

if current_user is None:
	return redirect(url_for('login'))

The second if condition checks whether the requested user in /profile/<username> endpoint exists inside our list of registered users. If the user is not present, then the developer is throwing message that User Profile not found..

if username in users:
	...
else:
	return "User profile not found."

But, wait! If the user requested is present, the developer is taking the profile details directly and showing it to the user.

profile = users[username]
return render_template('profile.html', username=username, profile=profile)

Bug

The developer forgot to add a check whether the current_user i.e. logged in user and the requested username is same.

Let’s try to mitigate the above said issue:

if username in users:
	profile = users[username]
	if username == current_user:
		return render_template('profile.html', username=username, profile=profile)
	else:
		return "You are not authorized to view this page."
else:
	return "User profile not found."

Now, let’s try to access the alice profile, location at /profile/alice.

Voila! We have successfully mitigated the IDOR vulnerability.

Question

A vulnerability still exists in the above code. Are you able to identify it. Hint: Information disclosure. Join our discord channel to tell us your bug hunting story.