I am trying to decide how to implement image uploading functionality on my flask app. I am currently using Flask-Uploads to get the job done and it seems to work pretty well. However, I have no idea if it is very secure, which could be a huge issue since file uploading is involved. Flask-Uploads doesn't really provide detailed information on the implementation of their service, so I haven't gained any insight by looking through the documentation. However, I saw that on Flask's official documentation they included an example of file uploads using Werkzeug, which seems to have some extra methods intended for file security. I can't seem to find anything on the web that sheds light on which one is more secure. Has anyone here with more web security experience ever examined one or both of these alternatives and come to a definite conclusion on this issue?
Flask-Uploads is actually using the patterns found in Flask's documentation for file upload handling. It uses werkzeug.secure_filename
, it provides a way to set MAX_CONTENT_LENGTH
if, for some reason, you are using Flask 0.5 or older, and it provides a way to validate files based on their extension.
In fact, Flask's documentation actually explicitly suggests using Flask-Uploads:
Because the common pattern for file uploads exists almost unchanged in all applications dealing with uploads, there is a Flask extension called Flask-Uploads that implements a full fledged upload mechanism with white and blacklisting of extensions and more.
I ended up with using my own secure_filename
in my case that supports Arabic:
import os
import re
import unicodedata
# edit this to add more characters
_filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9\u0600-\u06FF_.-]")
_windows_device_files = {
"CON",
"PRN",
"AUX",
"NUL",
*(f"COM{i}" for i in range(10)),
*(f"LPT{i}" for i in range(10)),
}
def secure_filename(filename: str) -> str:
# Normalize the filename to handle UTF-8 characters
filename = unicodedata.normalize("NFKC", filename)
# Replace path separators with spaces
for sep in os.sep, os.path.altsep:
if sep:
filename = filename.replace(sep, " ")
# Remove unsafe characters
filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip("._")
# Ensure the filename is not a reserved device name on Windows
if os.name == "nt" and filename and filename.split(".")[0].upper() in _windows_device_files:
filename = f"_{filename}"
return filename
UTF-8 Support
def secure_filename(filename):
if isinstance(filename, text_type):
from unicodedata import normalize
filename = normalize('NFKD', filename).encode('utf-8', 'ignore')
if not PY2:
filename = filename.decode('utf-8')
for sep in os.path.sep, os.path.altsep:
if sep:
filename = filename.replace(sep, ' ')
filename = str(_filename_gbk_strip_re.sub('', '_'.join(
filename.split()))).strip('._')
if os.name == 'nt' and filename and \
filename.split('.')[0].upper() in _windows_device_files:
filename = '_' + filename
return filename
Explanation
So, secure_filename
only supports ASCII, the reason suggested is for maximum portability according to the Docs, meaning if a filename has japanese characters it return an empty filename, in that case the following code derived from the function above might help.
Documentation: https://werkzeug.palletsprojects.com/en/2.2.x/utils/
Original Function
def secure_filename(filename: str) -> str:
r"""Pass it a filename and it will return a secure version of it. This
filename can then safely be stored on a regular file system and passed
to :func:`os.path.join`. The filename returned is an ASCII only string
for maximum portability.
On windows systems the function also makes sure that the file is not
named after one of the special device files.
>>> secure_filename("My cool movie.mov")
'My_cool_movie.mov'
>>> secure_filename("../../../etc/passwd")
'etc_passwd'
>>> secure_filename('i contain cool \xfcml\xe4uts.txt')
'i_contain_cool_umlauts.txt'
The function might return an empty filename. It's your responsibility
to ensure that the filename is unique and that you abort or
generate a random filename if the function returned an empty one.
.. versionadded:: 0.5
:param filename: the filename to secure
"""
filename = unicodedata.normalize("NFKD", filename)
filename = filename.encode("ascii", "ignore").decode("ascii")
for sep in os.sep, os.path.altsep:
if sep:
filename = filename.replace(sep, " ")
filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip(
"._"
)
# on nt a couple of special files are present in each folder. We
# have to ensure that the target file is not such a filename. In
# this case we prepend an underline
if (
os.name == "nt"
and filename
and filename.split(".")[0].upper() in _windows_device_files
):
filename = f"_{filename}"
return filename
I do recommend also checking out Flask-Uploads because as said above follows the same patterns provided inside the secure_filename function in werkzeug. I added the UTF-8 support pointing out on the issues, you might face and the source code so you can see how exactly werkzeug manages to provide secure filenames.
secure_filename
in the werkzeug
module. Correct me if I am wrong. I will update the code accordingly. Thanks. –
Loyalty filename
to be a text_type
? Why did you change os.sep
to os.path.sep
? –
Uxorious _filename_gbk_strip_re
supposed to be? It seems "borrowed" from this unique Google result gist.github.com/xiazhibin/f158629fd024c6c453dcdd175a142047 and is just a bunch of chinese unicode symbols. Your code is nowhere near full UTF-8 support. –
Uxorious © 2022 - 2025 — McMap. All rights reserved.