Flask File Handling: Werkzeug Vs Flask-Uploads
Asked Answered
K

3

5

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?

Klapp answered 28/10, 2014 at 1:28 Comment(2)
What do you mean by security? What are you worried about?Jaunitajaunt
Someone submitting a file with the name ''../../../../.bashrc'' or something like that. I just want to make sure Flask-Uploads isn't leaving the obvious holes open.Klapp
P
8

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.

Parole answered 28/10, 2014 at 12:58 Comment(2)
Flask-Uploads is deprecated in the latest versionChemoreceptor
@Chemoreceptor do you have a link for a reference to the deprecation?Salbu
B
0

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



Buffum answered 24/8, 2024 at 11:4 Comment(0)
L
-1

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.

Loyalty answered 28/1, 2022 at 19:22 Comment(6)
How is this secure? What did you change and why? Please don't simply dump some code without explanation and reasoning.Uxorious
@Uxorious I actually do not remember giving this answer but I think that is secure as I derived the logic from secure_filename in the werkzeug module. Correct me if I am wrong. I will update the code accordingly. Thanks.Loyalty
@Uxorious this add's support for UTF-8 named encoding's while retaining the same code base as werkzeug, it might help out in some cases where you want to keep that information.Loyalty
@Uxorious Please check the Answer now :thumbsup:Loyalty
Thanks! There are several changes with no obvious reason. Why do you test the filename to be a text_type? Why did you change os.sep to os.path.sep?Uxorious
And what is _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.