Skip to content

Commit f8209a6

Browse files
fix: Restrict upload of binary or unknown file types by default (#1507)
* Fix #1377 * Bump to 3.2.0 * fix: Deny upload of binary or unknown file types * Update tests * Update validation.rst * Update docs/validation.rst Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Add test that binary uploads fail by default * Add migration info --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
1 parent d72520c commit f8209a6

File tree

6 files changed

+87
-6
lines changed

6 files changed

+87
-6
lines changed

CHANGELOG.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ CHANGELOG
754754

755755

756756
0.5.4a1
757-
=======
757+
========
758758

759759
* Adds description field.
760760

README.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,26 @@ Documentation
4949
Please head over to the separate `documentation <https://django-filer.readthedocs.io/en/latest/index.html>`_
5050
for all the details on how to install, configure and use django-filer.
5151

52+
Upgrading
53+
=========
54+
55+
Version 3.3
56+
-----------
57+
58+
django-filer version 3 contains a change in security policy for file uploads.
59+
**By default, binary file or files of unknown type are not allowed to be uploaded.**
60+
To allow upload of binary files in your project, add
61+
62+
.. code-block:: python
63+
64+
FILER_REMOVE_FILE_VALIDATORS = [
65+
"application/octet-stream",
66+
]
67+
68+
to your project's settings. Be aware that binary files always are a security risk.
69+
See the documentation for more information on how to configure file upload validators,
70+
e.g., running files through a virus checker.
71+
5272

5373
.. |pypi| image:: https://badge.fury.io/py/django-filer.svg
5474
:target: http://badge.fury.io/py/django-filer

docs/validation.rst

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ files with the mime type ``image/svg+xml``. Those files are dangerous since
5454
they are executed by a browser without any warnings.
5555

5656
Validation hooks do not restrict the upload of other executable files
57-
(like ``*.exe`` or shell scripts). Those are not automatically executed
57+
(like ``*.exe`` or shell scripts). **Those are not automatically executed
5858
by the browser but still present a point of attack, if a user saves them
59-
to disk and executes them locally.
59+
to disk and executes them locally.**
6060

6161
You can release validation restrictions by setting
6262
``FILER_REMOVE_FILE_VALIDATORS`` to a list of mime types to be removed from
@@ -111,7 +111,7 @@ This just rejects any file for upload. By default this happens for HTML files
111111
112112
This validator rejects any SVG file that contains the bytes ``<script`` or
113113
``javascript:``. This probably is a too strict criteria, since those bytes
114-
might be part of a legitimate say string. The above code is a simplification
114+
might be part of a legitimate string. The above code is a simplification
115115
the actual code also checks for occurrences of event attribute like
116116
``onclick="..."``.
117117

@@ -144,10 +144,11 @@ a malicious file unknowingly.
144144
FILER_REMOVE_FILE_VALIDATORS = [
145145
"text/html",
146146
"image/svg+xml",
147+
"application/octet-stream",
147148
]
148149
149-
No HTML upload and restricted SVG upload
150-
........................................
150+
No HTML upload and restricted SVG upload, no binary or unknown file upload
151+
...........................................................................
151152

152153
This is the default setting. It will deny any SVG file that might contain
153154
Javascript. It is prone to false positives (i.e. files being rejected that
@@ -176,6 +177,8 @@ in the user's browser.
176177
"image/svg+xml": ["filer.validation.deny"],
177178
}
178179
180+
(Still not binary or unknown file upload)
181+
179182
Experimental SVG sanitization
180183
.............................
181184

@@ -259,3 +262,38 @@ You can use it to distinguish validation for certain user groups if needed.
259262

260263
If you distinguish validation by the mime type, remember to register the
261264
validator function for all relevant mime types.
265+
266+
267+
Checking uploads for viruses using ClamAV
268+
-----------------------------------------
269+
270+
If you have ClamAV installed and use `django-clamd <https://github.com/vstoykov/django-clamd>`_
271+
you can add a validator that checks for viruses in uploaded files.
272+
273+
.. code-block:: python
274+
275+
FILER_REMOVE_FILE_VALIDATORS = ["application/octet-stream"]
276+
FILER_ADD_FILE_VALIDATORS = {
277+
"application/octet-stream": ["my_validator_app.validators.validate_octet_stream"],
278+
}
279+
280+
281+
.. code-block:: python
282+
283+
def validate_octet_stream(file_name: str, file: typing.IO, owner: User, mime_type: str) -> None:
284+
"""Octet streams are binary files without a specific mime type. They are run through
285+
a virus check."""
286+
try:
287+
from django_clamd.validators import validate_file_infection
288+
289+
validate_file_infection(file)
290+
except (ModuleNotFoundError, ImportError):
291+
raise FileValidationError(
292+
_('File "{file_name}": Virus check for binary/unknown file not available').format(file_name=file_name)
293+
)
294+
295+
.. note::
296+
297+
Virus-checked files still might contain executable code. While the code is not
298+
executed by the browser, a user might still download the file and execute it
299+
manually.

filer/contrib/clamav/__init__.py

Whitespace-only changes.

filer/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ def update_server_settings(settings, defaults, s, t):
292292
FILE_VALIDATORS = {
293293
"text/html": ["filer.validation.deny_html"],
294294
"image/svg+xml": ["filer.validation.validate_svg"],
295+
"application/octet-stream": ["filer.validation.deny"],
295296
}
296297

297298
remove_mime_types = getattr(settings, "FILER_REMOVE_FILE_VALIDATORS", [])

tests/test_admin.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import django
55
import django.core.files
6+
from django.apps import apps
67
from django.conf import settings
78
from django.contrib import admin
89
from django.contrib.admin import helpers
@@ -484,6 +485,10 @@ def test_filer_upload_file_no_folder(self, extra_headers={}):
484485
self.assertEqual(stored_image.mime_type, 'image/jpeg')
485486

486487
def test_filer_upload_binary_data(self, extra_headers={}):
488+
config = apps.get_app_config("filer")
489+
490+
validators = config.FILE_VALIDATORS # Remember the validators
491+
config.FILE_VALIDATORS = {} # Remove deny for application/octet-stream
487492
self.assertEqual(File.objects.count(), 0)
488493
with open(self.binary_filename, 'rb') as fh:
489494
file_obj = django.core.files.File(fh)
@@ -494,12 +499,29 @@ def test_filer_upload_binary_data(self, extra_headers={}):
494499
'jsessionid': self.client.session.session_key
495500
}
496501
self.client.post(url, post_data, **extra_headers)
502+
config.FILE_VALIDATORS = validators # Reset validators
503+
497504
self.assertEqual(Image.objects.count(), 0)
498505
self.assertEqual(File.objects.count(), 1)
499506
stored_file = File.objects.first()
500507
self.assertEqual(stored_file.original_filename, self.binary_name)
501508
self.assertEqual(stored_file.mime_type, 'application/octet-stream')
502509

510+
def test_filer_upload_binary_data_fails_by_default(self, extra_headers={}):
511+
self.assertEqual(File.objects.count(), 0)
512+
with open(self.binary_filename, 'rb') as fh:
513+
file_obj = django.core.files.File(fh)
514+
url = reverse('admin:filer-ajax_upload')
515+
post_data = {
516+
'Filename': self.binary_name,
517+
'Filedata': file_obj,
518+
'jsessionid': self.client.session.session_key
519+
}
520+
self.client.post(url, post_data, **extra_headers)
521+
522+
self.assertEqual(Image.objects.count(), 0)
523+
self.assertEqual(File.objects.count(), 0)
524+
503525
def test_filer_ajax_upload_file(self):
504526
self.assertEqual(Image.objects.count(), 0)
505527
folder = Folder.objects.create(name='foo')

0 commit comments

Comments
 (0)