This exercise uses a simple web application written in Python using the Flask microframework. The second half of the exercise involves fixing the vulnerability in the application, and you will find this part easier if you already have some experience with Python and Flask.
If you have no such experience, there are plenty of quick tutorials out there. Flask’s own Quickstart introduction gives you most of what you need to know. If you don’t want to work through that, team up with someone who has used Flask and do the second part of the exercise together.
authbypass.zip and unzip it into a new
directory created for this exercise. This will give you a vulnerable web
application, in the file
app.py. Open a terminal window and cd to the
directory containing this file.
To run the app, you will need to use Python 3, in an environment where Flask is available. You can get this on SoC Linux machines by activating the Anaconda3 Python distribution, like so:
module load legacy-eng module add anaconda3/2020.11
After you’ve done this, enter the following commands:
export FLASK_APP=app.py export FLASK_DEBUG=1 flask run
As noted in Exercise 17, these commands assume the use of a Linux, macOS or WSL command line environment. Adjust them as described in that exercise if you are using the standard Windows cmd shell or Powershell.
With the vulnerable application running, visit
in a web browser. You should see a login page. Enter
the username and anything other than
user for the password and
click Log In. You should see the login form redisplayed with an error
Try again, this time entering
user for both username and password.
This should take you to the user’s home page.
Go to the browser address bar and reenter
This will forward you to the login page again. Now enter
both username and password, then click Log In. This should take
you to home page for the administrator.
At first glance, this application seems to be authenticating users correctly, but all is not what it seems…
Back in the terminal window, Press
Ctrl+C to kill the server, then
flask run to restart it.
http://localhost:5000 in the browser address bar again.
After you’ve been redirected to the login page, change the URL in the
address bar to
http://localhost:5000/user and press Enter.
Notice how you are taken to the user’s home page without having to
supply their username and password!
Repeat, this time editing the URL to
Again, you are able avoid entering a username and password.
This is even more serious because the admin page has elevated
privileges – simulated here by the ‘Do Dangerous Stuff’ button.
It’s time to find out what is going on. Kill the server with
app.py in a text editor and examine the application’s code.
You can see here that the
home() function handles visits to the root
URL of the application by redirecting to a
login() function presents the login form to the user and also
handles form input, using the latter to decide where the user should be
Now look at the
admin() functions. Here’s the code for
@app.route("/admin") def admin(): return render_template("admin.html")
/admin as a path that triggers the
and this simply returns the administrator page to the browser without
checking that the visitor has actually logged in as the administrator.
The second part of this exercise will implement a simple fix to the vulnerability seen earlier. The approach used here illustrates the principles but should not be regarded as an especially good solution!
admin() functions need to know whether the initiator
of the request has been authenticated, and who the initiator is; only
then can they decide whether to authorize the request or not. Information
therefore needs to be passed from the
login() function to the
admin() functions. We can use sessions to do this.
Start by editing
app.py and adding an
import statement for Flask’s
from flask import session
You also need to give the app a secret key that it can use to sign
session cookies, so add a line of code that initialises
to a random string of characters.
login() so that it stores the username in the
This object behaves like a Python dictionary, so you can do this with
session["username"] = username
Obviously, this should be done only if the user has authenticated successfully.
admin() so that they use the
session object to
check the identity of the logged-in user before returning the relevant web
page. Remember that
username might not be in the
if the user hasn’t authenticated!
If the correct value for
username is not present, both functions
should issue a 401 Unauthorized HTTP error. The easiest way of
doing this in Flask is to call the
abort function like so:
Note that you’ll need to import this function from the
just as you did with the
Optional: skip this step if you want…
Implement a custom handler for 401 errors. Add the following to the
@app.errorhandler(401) def unauthorized(error): return render_template("unauthorized.html"), 401
Then create a suitable template named
unauthorized.html in the
templates directory. Use the other templates in that directory to
guide you. For a consistent style, try representing the error message
as a Bootstrap 5 alert.
It’s important that users can log out, and that the
session object no
longer holds their username once they’ve done so. Implement this by
@app.route("/logout") def logout(): # (1) Remove username from session # (2) Redirect to login page
Add the code suggested by the comments above, then add ‘Log Out’ buttons to the user and admin templates. The following HTML will do the trick:
<a href="/logout" class="btn btn-primary">Log Out</a>
flask run to run the application again. Make sure that it works
as expected and that bypassing authentication is no longer possible.
NOTE: This demonstrates a solution but isn’t the best way of fixing the
problem! Any web framework worth its salt will provide you with better
and more secure options for managing users and logins. For example,
Flask provides the Flask-Login extension, with which you can
indicate that login is required by adding the
to your request handlers:
from flask_login import current_user, login_required ... @app.route("/admin") @login_required def admin(): if current_user.get_id() == "admin": return render_template("admin.html") else: abort(401)
A request made for
/admin from an unauthenticated visitor results in
a redirect to a login handler function (not shown), after which the
visitor will be sent back here.