The source code can be obtained by exploiting the path traversal vulnerability. To view the source of the web application, browse the URL http://127.0.0.1:5000/employee?filename=../app.py.

from flask import Flask, request, render_template, abort
import os
 
app = Flask(__name__)
 
# Route for the index page
@app.route('/')
def index():
    return render_template('index.html')
 
# Vulnerable route to demonstrate path traversal or local file inclusion
@app.route('/employee')
def view_file():
    filename = request.args.get('filename', '')
    file_path = os.path.join('./uploads', filename)
 
    # Simulate vulnerability to path traversal or local file inclusion
    try:
        with open(file_path, 'r') as file:
            file_content = file.read()
        return f"<pre>{file_content}</pre>"
    except FileNotFoundError:
        abort(404)
    except IsADirectoryError:
        abort(400)
 
 
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

The above source code is a python based flask application. Let’s try to identify where the bug exists which leads path traversal vulnerability in our source code.

The below code maps the URL path ’/’ to the index function which returns/renders the index.html, which seems like does not contain path traversal vulnerability.

@app.route('/')
def index():
    return render_template('index.html')

The quickest way to detect a path traversal vulnerability to spot the source code using file operations

Now, let’s analyse the view_file function’s definition mounted at endpoint /employee (this function seems like our candidate, as our vulnerability existed on /employee).

@app.route('/employee')
def view_file():
    filename = request.args.get('filename', '')
    file_path = os.path.join('./uploads', filename)
 
    # Simulate vulnerability to path traversal or local file inclusion
    try:
        with open(file_path, 'r') as file:
            file_content = file.read()
        return f"<pre>{file_content}</pre>"
    except FileNotFoundError:
        abort(404)
    except IsADirectoryError:
        abort(400)

In the above function, the developer is using os.path.join construct to create the absolute path from the user-input filename, but the developer failed to check that the file being requested is in current context or not.

Mitigation

The following code snippet fixes the same by using the os.path.commonpath function to check whether the path is in common between our current working directory and the file requested.

# Vulnerable route to demonstrate path traversal or local file inclusion
@app.route('/employee')
def view_file():
    basedir = os.getcwd()
    filename = request.args.get('filename', '')
    file_path = os.path.join('./uploads', filename)
    file_path = os.path.realpath(file_path)
  
    # Simulate vulnerability to path traversal or local file inclusion
    try:
        with open(file_path, 'r') as file:
            if basedir == os.path.commonpath((basedir, file_path)):
                file_content = file.read()
                return f"<pre>{file_content}</pre>"
        abort(404)
    except FileNotFoundError:
        abort(404)
    except IsADirectoryError:
        abort(400)
 

After, making the appropriate change, you can navigate to our vulnerable URL endpoint for accessing /etc/passwd file i.e. http://127.0.0.1:5000/employee?filename=../../../../etc/passwd, you will be encountered with 404 Not Found error.