mirror of
https://github.com/django/django.git
synced 2025-09-12 20:19:25 +00:00
[1.4.x] Fixed a remote code execution vulnerabilty in URL reversing.
Thanks Benjamin Bach for the report and initial patch. This is a security fix; disclosure to follow shortly. Backport of 8b93b31487d6d3b0fcbbd0498991ea0db9088054 from master
This commit is contained in:
parent
ca3927dfb9
commit
c1a8c420fe
@ -230,6 +230,10 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||||||
self._reverse_dict = {}
|
self._reverse_dict = {}
|
||||||
self._namespace_dict = {}
|
self._namespace_dict = {}
|
||||||
self._app_dict = {}
|
self._app_dict = {}
|
||||||
|
# set of dotted paths to all functions and classes that are used in
|
||||||
|
# urlpatterns
|
||||||
|
self._callback_strs = set()
|
||||||
|
self._populated = False
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
|
return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
|
||||||
@ -240,6 +244,15 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||||||
apps = {}
|
apps = {}
|
||||||
language_code = get_language()
|
language_code = get_language()
|
||||||
for pattern in reversed(self.url_patterns):
|
for pattern in reversed(self.url_patterns):
|
||||||
|
if hasattr(pattern, '_callback_str'):
|
||||||
|
self._callback_strs.add(pattern._callback_str)
|
||||||
|
elif hasattr(pattern, '_callback'):
|
||||||
|
callback = pattern._callback
|
||||||
|
if not hasattr(callback, '__name__'):
|
||||||
|
lookup_str = callback.__module__ + "." + callback.__class__.__name__
|
||||||
|
else:
|
||||||
|
lookup_str = callback.__module__ + "." + callback.__name__
|
||||||
|
self._callback_strs.add(lookup_str)
|
||||||
p_pattern = pattern.regex.pattern
|
p_pattern = pattern.regex.pattern
|
||||||
if p_pattern.startswith('^'):
|
if p_pattern.startswith('^'):
|
||||||
p_pattern = p_pattern[1:]
|
p_pattern = p_pattern[1:]
|
||||||
@ -260,6 +273,7 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||||||
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
|
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
|
||||||
for app_name, namespace_list in pattern.app_dict.items():
|
for app_name, namespace_list in pattern.app_dict.items():
|
||||||
apps.setdefault(app_name, []).extend(namespace_list)
|
apps.setdefault(app_name, []).extend(namespace_list)
|
||||||
|
self._callback_strs.update(pattern._callback_strs)
|
||||||
else:
|
else:
|
||||||
bits = normalize(p_pattern)
|
bits = normalize(p_pattern)
|
||||||
lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
|
lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
|
||||||
@ -268,6 +282,7 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||||||
self._reverse_dict[language_code] = lookups
|
self._reverse_dict[language_code] = lookups
|
||||||
self._namespace_dict[language_code] = namespaces
|
self._namespace_dict[language_code] = namespaces
|
||||||
self._app_dict[language_code] = apps
|
self._app_dict[language_code] = apps
|
||||||
|
self._populated = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def reverse_dict(self):
|
def reverse_dict(self):
|
||||||
@ -356,7 +371,12 @@ class RegexURLResolver(LocaleRegexProvider):
|
|||||||
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
|
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
|
||||||
if args and kwargs:
|
if args and kwargs:
|
||||||
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
|
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
|
||||||
|
|
||||||
|
if not self._populated:
|
||||||
|
self._populate()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if lookup_view in self._callback_strs:
|
||||||
lookup_view = get_callable(lookup_view, True)
|
lookup_view = get_callable(lookup_view, True)
|
||||||
except (ImportError, AttributeError), e:
|
except (ImportError, AttributeError), e:
|
||||||
raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
|
raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
def view(request):
|
||||||
|
"""Stub view"""
|
||||||
|
pass
|
@ -1,8 +1,11 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Unit tests for reverse URL lookups.
|
Unit tests for reverse URL lookups.
|
||||||
"""
|
"""
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
||||||
from django.core.urlresolvers import (reverse, resolve, NoReverseMatch,
|
from django.core.urlresolvers import (reverse, resolve, NoReverseMatch,
|
||||||
@ -267,6 +270,25 @@ class ReverseShortcutTests(TestCase):
|
|||||||
self.assertEqual(res['Location'], '/foo/')
|
self.assertEqual(res['Location'], '/foo/')
|
||||||
res = redirect('http://example.com/')
|
res = redirect('http://example.com/')
|
||||||
self.assertEqual(res['Location'], 'http://example.com/')
|
self.assertEqual(res['Location'], 'http://example.com/')
|
||||||
|
# Assert that we can redirect using UTF-8 strings
|
||||||
|
res = redirect('/æøå/abc/')
|
||||||
|
self.assertEqual(res['Location'], '/%C3%A6%C3%B8%C3%A5/abc/')
|
||||||
|
# Assert that no imports are attempted when dealing with a relative path
|
||||||
|
# (previously, the below would resolve in a UnicodeEncodeError from __import__ )
|
||||||
|
res = redirect('/æøå.abc/')
|
||||||
|
self.assertEqual(res['Location'], '/%C3%A6%C3%B8%C3%A5.abc/')
|
||||||
|
res = redirect('os.path')
|
||||||
|
self.assertEqual(res['Location'], 'os.path')
|
||||||
|
|
||||||
|
def test_no_illegal_imports(self):
|
||||||
|
# modules that are not listed in urlpatterns should not be importable
|
||||||
|
redirect("urlpatterns_reverse.nonimported_module.view")
|
||||||
|
self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules)
|
||||||
|
|
||||||
|
def test_reverse_by_path_nested(self):
|
||||||
|
# Views that are added to urlpatterns using include() should be
|
||||||
|
# reversable by doted path.
|
||||||
|
self.assertEqual(reverse('regressiontests.urlpatterns_reverse.views.nested_view'), '/includes/nested_path/')
|
||||||
|
|
||||||
def test_redirect_view_object(self):
|
def test_redirect_view_object(self):
|
||||||
from .views import absolute_kwargs_view
|
from .views import absolute_kwargs_view
|
||||||
@ -510,4 +532,3 @@ class ErroneousViewTests(TestCase):
|
|||||||
self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_inner/')
|
self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_inner/')
|
||||||
self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/')
|
self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/')
|
||||||
self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable/')
|
self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable/')
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ from .views import empty_view, absolute_kwargs_view
|
|||||||
|
|
||||||
other_patterns = patterns('',
|
other_patterns = patterns('',
|
||||||
url(r'non_path_include/$', empty_view, name='non_path_include'),
|
url(r'non_path_include/$', empty_view, name='non_path_include'),
|
||||||
|
url(r'nested_path/$', 'regressiontests.urlpatterns_reverse.views.nested_view'),
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
|
@ -16,6 +16,10 @@ def absolute_kwargs_view(request, arg1=1, arg2=2):
|
|||||||
def defaults_view(request, arg1, arg2):
|
def defaults_view(request, arg1, arg2):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def nested_view(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def erroneous_view(request):
|
def erroneous_view(request):
|
||||||
import non_existent
|
import non_existent
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user