Link Search Menu Expand Document

Unrestricted File Download in Python

Play SecureFlag Play Python Labs on this vulnerability with SecureFlag!

Vulnerable Example

The snippet below defines a Flask endpoint that serves files from the /opt/wwwdata/assets/ asset folder depending on the file name passed as asset_name.

asset_folder = '/opt/wwwdata/assets/'

@app.route('/assets')
def get_asset():
    asset_name = request.args.get('asset_name')
    if not asset_name:
        return 404
    return send_file(os.path.join(asset_folder, asset_name))

Since asset_name is controlled by the user, it is possible to conduct a path traversal attack and escape the intended directory, for example by using ../../../../../../etc/passwd as the file asset name.

This results in an unexpected file disclosure since the absolute path /opt/wwwdata/assets/../../../../../etc/passwd is canonicalized by the operating system as /etc/passwd.

Prevention

To validate that a path does not point to an unintended location, use os.path.abspath() to get the absolute path, and then check that it starts as expected.

asset_folder = '/opt/wwwdata/assets/'

@app.route('/assets')
def get_asset():
    asset_name = request.args.get('asset_name')
    if not asset_name:
        return 404

    absolute_file_path = os.path.abspath(os.path.join(asset_folder, asset_name))
    if not absolute_file_path.startswith(asset_folder):
        return 403

    return send_file(absolute_file_path)

Alternatively, to sanitize the path, it is possible to use Flask’s component werkzeug.secure_filename() to properly sanitize any user-provided input.

from werkzeug.utils import secure_filename

asset_folder = '/opt/wwwdata/assets/'

@app.route('/assets')
def get_asset():
    asset_name = request.args.get('asset_name')
    if not asset_name:
        return 404

    asset_name = secure_filename(asset_name)
    return send_file(os.path.join(asset_folder, asset_name))

The previous attack would result in the /opt/wwwdata/assets/etc_passwd file being read.

FastAPI

In FastAPI, the StaticFiles function is used to serve files from a directory securely. When you mount the StaticFiles instance to the assets path, it takes care of handling the files in that directory, preventing path traversal attacks by ensuring that only files within the specified directory can be served.

app = FastAPI()
app.mount("/assets", StaticFiles(directory="/opt/wwwdata/assets"), name="assets")

References

Owasp - Path Traversal FastAPI Static Files