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 requestedusername
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.