Skip to content

Commit 553779c

Browse files
committed
[1.6.x] Prevented views.static.serve() from using large memory on large files.
This is a security fix. Disclosure following shortly.
1 parent 72e0b03 commit 553779c

File tree

5 files changed

+45
-2
lines changed

5 files changed

+45
-2
lines changed

django/views/static.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
from django.utils.six.moves.urllib.parse import unquote
1818
from django.utils.translation import ugettext as _, ugettext_noop
1919

20+
STREAM_CHUNK_SIZE = 4096
21+
22+
2023
def serve(request, path, document_root=None, show_indexes=False):
2124
"""
2225
Serve static files below a given point in the directory structure.
@@ -60,7 +63,8 @@ def serve(request, path, document_root=None, show_indexes=False):
6063
return HttpResponseNotModified()
6164
content_type, encoding = mimetypes.guess_type(fullpath)
6265
content_type = content_type or 'application/octet-stream'
63-
response = CompatibleStreamingHttpResponse(open(fullpath, 'rb'),
66+
f = open(fullpath, 'rb')
67+
response = CompatibleStreamingHttpResponse(iter(lambda: f.read(STREAM_CHUNK_SIZE), b''),
6468
content_type=content_type)
6569
response["Last-Modified"] = http_date(statobj.st_mtime)
6670
if stat.S_ISREG(statobj.st_mode):

docs/releases/1.4.18.txt

+15
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ from a XSS attack. This bug doesn't affect Django currently, since we only put
4545
this URL into the ``Location`` response header and browsers seem to ignore
4646
JavaScript there.
4747

48+
Denial-of-service attack against ``django.views.static.serve``
49+
==============================================================
50+
51+
In older versions of Django, the :func:`django.views.static.serve` view read
52+
the files it served one line at a time. Therefore, a big file with no newlines
53+
would result in memory usage equal to the size of that file. An attacker could
54+
exploit this and launch a denial-of-service attack by simultaneously requesting
55+
many large files. This view now reads the file in chunks to prevent large
56+
memory usage.
57+
58+
Note, however, that this view has always carried a warning that it is not
59+
hardened for production use and should be used only as a development aid. Now
60+
may be a good time to audit your project and serve your files in production
61+
using a real front-end web server if you are not doing so.
62+
4863
Bugfixes
4964
========
5065

docs/releases/1.6.10.txt

+15
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,18 @@ provide safe redirect targets and put such a URL into a link, they could suffer
4343
from a XSS attack. This bug doesn't affect Django currently, since we only put
4444
this URL into the ``Location`` response header and browsers seem to ignore
4545
JavaScript there.
46+
47+
Denial-of-service attack against ``django.views.static.serve``
48+
==============================================================
49+
50+
In older versions of Django, the :func:`django.views.static.serve` view read
51+
the files it served one line at a time. Therefore, a big file with no newlines
52+
would result in memory usage equal to the size of that file. An attacker could
53+
exploit this and launch a denial-of-service attack by simultaneously requesting
54+
many large files. This view now reads the file in chunks to prevent large
55+
memory usage.
56+
57+
Note, however, that this view has always carried a warning that it is not
58+
hardened for production use and should be used only as a development aid. Now
59+
may be a good time to audit your project and serve your files in production
60+
using a real front-end web server if you are not doing so.

tests/view_tests/media/long-line.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua hic tempora est veritatis culpa fugiat doloribus fugit in sed harum veniam porro eveniet maxime labore assumenda non illum possimus aut vero laudantium cum magni numquam dolorem explicabo quidem quasi nesciunt ipsum deleniti facilis neque similique nisi ad magnam accusamus quae provident dolor ab atque modi laboriosam fuga suscipit ea beatae ipsam consequatur saepe dolore nulla error quo iusto expedita nemo commodi aspernatur aliquam enim reiciendis rerum necessitatibus recusandae sint amet placeat temporibus autem iste deserunt esse dolores reprehenderit doloremque pariatur velit maiores repellat dignissimos asperiores aperiam alias a corporis id praesentium voluptatibus soluta voluptatem sit molestiae quas odio facere nostrum laborum incidunt eaque nihil ullam rem mollitia at cumque iure tenetur tempore totam repudiandae quisquam quod architecto officia vitae consectetur cupiditate molestias delectus voluptates earum et impedit quibusdam odit sequi perferendis eius perspiciatis eos quam quaerat officiis sunt ratione consequuntur quia quis obcaecati repellendus exercitationem vel minima libero blanditiis eligendi minus dicta voluptas excepturi nam eum inventore voluptatum ducimus sapiente dolorum itaque ipsa qui omnis debitis voluptate quos aliquid accusantium ex illo corrupti ut adipisci natus animi distinctio optio nobis unde similique excepturi vero culpa molestias fugit dolorum non amet iure inventore nihil suscipit explicabo veritatis officiis distinctio nesciunt saepe incidunt reprehenderit porro vitae cumque alias ut deleniti expedita ratione odio magnam eligendi a nostrum laborum minus esse sit libero quaerat qui id illo voluptates soluta neque odit dolore consectetur ducimus nulla est nisi impedit quia sapiente ullam temporibus ipsam repudiandae delectus fugiat blanditiis maxime voluptatibus aspernatur ea ipsum quisquam sunt eius ipsa accusantium enim corporis earum sed sequi dicta accusamus dignissimos illum pariatur quos aut reiciendis obcaecati perspiciatis consequuntur nam modi praesentium cum repellat possimus iste atque quidem architecto recusandae harum eaque sint quae optio voluptate quod quasi beatae magni necessitatibus facilis aperiam repellendus nemo aliquam et quibusdam debitis itaque cupiditate laboriosam unde tempora commodi laudantium in placeat ad vel maiores aliquid hic tempore provident quas officia adipisci rem corrupti iusto natus eum rerum at ex quam eveniet totam dolor assumenda error eos doloribus labore fuga facere deserunt ab dolores consequatur veniam animi exercitationem asperiores mollitia minima numquam voluptatem voluptatum nobis molestiae voluptas omnis velit quis quo tenetur perferendis autem dolorem doloremque sequi vitae laudantium magnam quae adipisci expedita doloribus minus perferendis vero animi at quos iure facere nihil veritatis consectetur similique porro tenetur nobis fugiat quo ducimus qui soluta maxime placeat error sunt ullam quaerat provident eos minima ab harum ratione inventore unde sint dolorum deserunt veniam laborum quasi suscipit facilis eveniet voluptatibus est ipsum sapiente omnis vel repellat perspiciatis illo voluptate aliquid magni alias modi odit ea a voluptatem reiciendis recusandae mollitia eius distinctio amet atque voluptates obcaecati deleniti eligendi commodi debitis dolore laboriosam nam illum pariatur earum exercitationem velit in quas explicabo fugit asperiores itaque quam sit dolorem beatae quod cumque necessitatibus tempora dolores hic aperiam ex tempore ut neque maiores ad dicta voluptatum eum officia assumenda reprehenderit nisi cum molestiae et iusto quidem consequuntur repellendus saepe corrupti numquam culpa rerum incidunt dolor impedit iste sed non praesentium ipsam consequatur eaque possimus quia quibusdam excepturi aspernatur voluptas quisquam autem molestias aliquam corporis delectus nostrum labore nesciunt blanditiis quis enim accusamus nulla architecto fuga natus ipsa repudiandae cupiditate temporibus aut libero optio id officiis esse dignissimos odio totam doloremque accusantium nemo rem repudiandae aliquam accusamus autem minima reiciendis debitis quis ut ducimus quas dolore ratione neque velit repellat natus est error ea nam consequuntur rerum excepturi aspernatur quaerat cumque voluptatibus rem quasi eos unde architecto animi sunt veritatis delectus nulla at iusto repellendus dolorum obcaecati commodi earum assumenda quisquam cum officiis modi ab tempora harum vitae voluptatem explicabo alias maxime nostrum iure consectetur incidunt laudantium distinctio deleniti iste facere fugit libero illo nobis expedita perferendis labore similique beatae sint dicta dignissimos sapiente dolor soluta perspiciatis aut ad illum facilis totam necessitatibus eveniet temporibus reprehenderit quidem fugiat magni dolorem doloribus quibusdam eligendi fuga quae recusandae eum amet dolores asperiores voluptas inventore officia sit vel id vero nihil optio nisi magnam deserunt odit corrupti adipisci aliquid odio enim pariatur cupiditate suscipit voluptatum corporis porro mollitia eaque quia non quod consequatur ipsa nesciunt itaque exercitationem molestias molestiae atque in numquam quo ipsam nemo ex tempore ipsum saepe esse sed veniam a voluptates placeat accusantium quos laboriosam voluptate provident hic sequi quam doloremque eius impedit omnis possimus laborum tenetur praesentium et minus ullam blanditiis culpa qui aperiam maiores quidem numquam nulla

tests/view_tests/tests/test_static.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from django.test import TestCase
1010
from django.test.utils import override_settings
1111
from django.utils.http import http_date
12-
from django.views.static import was_modified_since
12+
from django.views.static import was_modified_since, STREAM_CHUNK_SIZE
1313

1414
from .. import urls
1515
from ..urls import media_dir
@@ -33,6 +33,14 @@ def test_serve(self):
3333
self.assertEqual(len(response_content), int(response['Content-Length']))
3434
self.assertEqual(mimetypes.guess_type(file_path)[1], response.get('Content-Encoding', None))
3535

36+
def test_chunked(self):
37+
"The static view should stream files in chunks to avoid large memory usage"
38+
response = self.client.get('/views/%s/%s' % (self.prefix, 'long-line.txt'))
39+
first_chunk = next(response.streaming_content)
40+
self.assertEqual(len(first_chunk), STREAM_CHUNK_SIZE)
41+
second_chunk = next(response.streaming_content)
42+
self.assertEqual(len(second_chunk), 1451)
43+
3644
def test_unknown_mime_type(self):
3745
response = self.client.get('/views/%s/file.unknown' % self.prefix)
3846
self.assertEqual('application/octet-stream', response['Content-Type'])

0 commit comments

Comments
 (0)