
Demonstration of CWE-22 Path Traversal in Flask-Uploads 0.2.1. For educational and security research purposes only. Tested on Python 3.11.
A critical path traversal vulnerability exists in Flask-Uploads version 0.2.1. The library fails to properly sanitize the filenames of uploaded files before saving them to the filesystem.
Specifically, the save() method in flask_uploads.py directly concatenates the upload destination path with the user-provided filename using os.path.join(). This allows an attacker to include directory traversal sequences (e.g., ../../) in the filename, enabling them to write files to arbitrary locations on the server file system.
File: flask_uploads.py (Line 132 in v0.2.1)
def save(self, storage, filename=None):
if filename is None:
filename = storage.filename
# VULNERABLE CODE: No sanitization of 'filename'
target = os.path.join(self.destination, filename)
storage.save(target)
return filename
The application relies on the developer to sanitize the filename, but the library documentation implies that it handles file uploads securely. By default, if a developer passes a raw filename from a request, it leads to a vulnerability.
ssh_host_key, configuration files)..php, .py) to a directory that is executed by the web server (like cgi-bin or a known static folder), an attacker can execute arbitrary commands on the server.An attacker can exploit this vulnerability by sending a POST request with a crafted filename:
POST /upload HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="../../../../../tmp/pwned.txt"
Content-Type: text/plain
HACKED
------WebKitFormBoundary--
If the server uses Flask-Uploads to save this file, it will be written to /tmp/pwned.txt instead of the intended upload directory.
The library should verify that the joined path is within the intended directory using os.path.abspath and startswith, or always use a sanitization function like werkzeug.utils.secure_filename.
Recommended Fix:
from werkzeug.utils import secure_filename
# ...
filename = secure_filename(filename)
target = os.path.join(self.destination, filename)