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?

Jeff Widman
  • 22,014
  • 12
  • 72
  • 88
Harrison
  • 830
  • 1
  • 15
  • 28
  • What do you mean by security? What are you worried about? – hamidfzm Oct 28 '14 at 07:31
  • 3
    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. – Harrison Oct 28 '14 at 14:40

2 Answers2

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.

Tri
  • 2,722
  • 5
  • 36
  • 65
Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
-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.

  • How is this secure? What did you change and why? Please don't simply dump some code without explanation and reasoning. – bugmenot123 Dec 30 '22 at 15:04
  • @bugmenot123 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. – Muneeb Ahmad Khurram Dec 30 '22 at 17:27
  • @bugmenot123 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. – Muneeb Ahmad Khurram Dec 30 '22 at 17:29
  • @bugmenot123 Please check the Answer now :thumbsup: – Muneeb Ahmad Khurram Dec 30 '22 at 17:33
  • 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`? – bugmenot123 Apr 03 '23 at 20:46
  • And what is `_filename_gbk_strip_re` supposed to be? It seems "borrowed" from this unique Google result https://gist.github.com/xiazhibin/f158629fd024c6c453dcdd175a142047 and is just a bunch of chinese unicode symbols. Your code is nowhere near full UTF-8 support. – bugmenot123 Apr 03 '23 at 20:50