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. In our vulnerable application, the endpoint which is vulnerable to CSRF attack is /update_bio POST request. Let’s try to find the file in which the function for this POST request is defined.

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

docker exec -it csrf /bin/bash

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

root@9c7fa193e734:/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, session
import secrets
 
app = Flask(__name__)
app.secret_key = secrets.token_hex(16)  # Needed for session
 
@app.route('/update_bio', methods=['GET', 'POST'])
def update_bio():
    if 'user' not in session:
        return redirect(url_for('login'))
 
    user = session['user']
 
    if request.method == 'POST':
        new_bio = request.form.get('bio')
        if new_bio:
            users[user]['bio'] = new_bio
        return redirect(url_for('profile', username=user))
 
    return render_template('update_bio.html', user=user, current_bio=users[user]['bio'])
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

Bug

The above flask based application code accepts all the POST requests, but it does not verify whether the request originated from a trusted source.

To protect from these type of CSRF attacks a common approach is to use CSRF token mechanism.

Note

A CSRF token mechanism is required to ensure that each request is intentionally initiated by the user and not forged by a third-party website. This tokens are randomised for each user session request or form submissions to prevent attackers from predicting or reusing them in forged requests.

Now let’s add the CSRF token mechanism in our sample python application. flask library natively does not provide CSRF protection mechanism, but an alternative flask-wtf library provides this functionality over flask. Let’s install the library using pip

pip install flask-wtf

Add CSRF protection to our app (in app.py file)

from flask import Flask, render_template, request, redirect, url_for, session
from flask_wtf import CSRFProtect
import secrets
 
app = Flask(__name__)
app.secret_key = secrets.token_hex(16)  # Needed for session
 
csrf = CSRFProtect(app)  # Enables CSRF protection

Now, we need to edit the .html files to incorporate the CSRF tokens in forms. The login.html and update_bio.html under templates directory uses HTML forms. Add the following line to add CSRF token as a hidden field within forms.

<form method="POST">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    <!-- other form fields -->
    <input type="submit" value="Submit">
</form>

Once both of the .html files are changed. Let’s try our malicious HTML file once again, and check whether it stills works or not?

Voila! We have successfully mitigated the CSRF vulnerability by adding the CSRF tokens in all the forms.