Skip to content

Commit

Permalink
[1.0.X] SECURITY ALERT: Corrected a problem with the Admin media hand…
Browse files Browse the repository at this point in the history
…ler that could lead to the exposure of system files. Thanks to Gary Wilson for the patch.

This is a security-related backport of r11351. A full announcement, as well as a backport 0.96.X will be forthcoming.


git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.0.X@11353 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
freakboy3742 committed Jul 29, 2009
1 parent f9249e4 commit df7f917
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 7 deletions.
3 changes: 1 addition & 2 deletions django/core/management/commands/runserver.py
Expand Up @@ -56,8 +56,7 @@ def inner_run():
translation.activate(settings.LANGUAGE_CODE)

try:
path = admin_media_path or django.__path__[0] + '/contrib/admin/media'
handler = AdminMediaHandler(WSGIHandler(), path)
handler = AdminMediaHandler(WSGIHandler(), admin_media_path)
run(addr, int(port), handler)
except WSGIServerException, e:
# Use helpful error messages instead of ugly tracebacks.
Expand Down
31 changes: 26 additions & 5 deletions django/core/servers/basehttp.py
Expand Up @@ -16,6 +16,7 @@
import urllib

from django.utils.http import http_date
from django.utils._os import safe_join

__version__ = "0.1"
__all__ = ['WSGIServer','WSGIRequestHandler']
Expand Down Expand Up @@ -621,11 +622,25 @@ def __init__(self, application, media_dir=None):
self.application = application
if not media_dir:
import django
self.media_dir = django.__path__[0] + '/contrib/admin/media'
self.media_dir = \
os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
else:
self.media_dir = media_dir
self.media_url = settings.ADMIN_MEDIA_PREFIX

def file_path(self, url):
"""
Returns the path to the media file on disk for the given URL.
The passed URL is assumed to begin with ADMIN_MEDIA_PREFIX. If the
resultant file path is outside the media directory, then a ValueError
is raised.
"""
# Remove ADMIN_MEDIA_PREFIX.
relative_url = url[len(self.media_url):]
relative_path = urllib.url2pathname(relative_url)
return safe_join(self.media_dir, relative_path)

def __call__(self, environ, start_response):
import os.path

Expand All @@ -636,19 +651,25 @@ def __call__(self, environ, start_response):
return self.application(environ, start_response)

# Find the admin file and serve it up, if it exists and is readable.
relative_url = environ['PATH_INFO'][len(self.media_url):]
file_path = os.path.join(self.media_dir, relative_url)
try:
file_path = self.file_path(environ['PATH_INFO'])
except ValueError: # Resulting file path was not valid.
status = '404 NOT FOUND'
headers = {'Content-type': 'text/plain'}
output = ['Page not found: %s' % environ['PATH_INFO']]
start_response(status, headers.items())
return output
if not os.path.exists(file_path):
status = '404 NOT FOUND'
headers = {'Content-type': 'text/plain'}
output = ['Page not found: %s' % file_path]
output = ['Page not found: %s' % environ['PATH_INFO']]
else:
try:
fp = open(file_path, 'rb')
except IOError:
status = '401 UNAUTHORIZED'
headers = {'Content-type': 'text/plain'}
output = ['Permission denied: %s' % file_path]
output = ['Permission denied: %s' % environ['PATH_INFO']]
else:
# This is a very simple implementation of conditional GET with
# the Last-Modified header. It makes media files a bit speedier
Expand Down
Empty file.
Empty file.
67 changes: 67 additions & 0 deletions tests/regressiontests/servers/tests.py
@@ -0,0 +1,67 @@
"""
Tests for django.core.servers.
"""

import os

import django
from django.test import TestCase
from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import AdminMediaHandler


class AdminMediaHandlerTests(TestCase):

def setUp(self):
self.admin_media_file_path = \
os.path.join(django.__path__[0], 'contrib', 'admin', 'media')
self.handler = AdminMediaHandler(WSGIHandler())

def test_media_urls(self):
"""
Tests that URLs that look like absolute file paths after the
settings.ADMIN_MEDIA_PREFIX don't turn into absolute file paths.
"""
# Cases that should work on all platforms.
data = (
('/media/css/base.css', ('css', 'base.css')),
)
# Cases that should raise an exception.
bad_data = ()

# Add platform-specific cases.
if os.sep == '/':
data += (
# URL, tuple of relative path parts.
('/media/\\css/base.css', ('\\css', 'base.css')),
)
bad_data += (
'/media//css/base.css',
'/media////css/base.css',
'/media/../css/base.css',
)
elif os.sep == '\\':
bad_data += (
'/media/C:\css/base.css',
'/media//\\css/base.css',
'/media/\\css/base.css',
'/media/\\\\css/base.css'
)
for url, path_tuple in data:
try:
output = self.handler.file_path(url)
except ValueError:
self.fail("Got a ValueError exception, but wasn't expecting"
" one. URL was: %s" % url)
rel_path = os.path.join(*path_tuple)
desired = os.path.normcase(
os.path.join(self.admin_media_file_path, rel_path))
self.assertEqual(output, desired,
"Got: %s, Expected: %s, URL was: %s" % (output, desired, url))
for url in bad_data:
try:
output = self.handler.file_path(url)
except ValueError:
continue
self.fail('URL: %s should have caused a ValueError exception.'
% url)

0 comments on commit df7f917

Please sign in to comment.