VYPR
Critical severityNVD Advisory· Published Apr 23, 2014· Updated May 6, 2026

CVE-2014-0472

CVE-2014-0472

Description

The django.core.urlresolvers.reverse function in Django before 1.4.11, 1.5.x before 1.5.6, 1.6.x before 1.6.3, and 1.7.x before 1.7 beta 2 allows remote attackers to import and execute arbitrary Python modules by leveraging a view that constructs URLs using user input and a "dotted Python path."

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
DjangoPyPI
< 1.4.111.4.11
DjangoPyPI
>= 1.5, < 1.5.61.5.6
DjangoPyPI
>= 1.6, < 1.6.31.6.3

Affected products

28
  • cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*+ 22 more
    • cpe:2.3:a:djangoproject:django:*:*:*:*:*:*:*:*range: <=1.4.10
    • cpe:2.3:a:djangoproject:django:1.4:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.1:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.2:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.3:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.4:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.5:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.6:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.7:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.8:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.4.9:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6.1:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.6.2:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.7:alpha1:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.7:alpha2:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.7:beta1:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.1:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.2:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.3:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.4:*:*:*:*:*:*:*
    • cpe:2.3:a:djangoproject:django:1.5.5:*:*:*:*:*:*:*
  • cpe:2.3:o:canonical:ubuntu_linux:10.04:-:lts:*:*:*:*:*+ 4 more
    • cpe:2.3:o:canonical:ubuntu_linux:10.04:-:lts:*:*:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:12.04:-:lts:*:*:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:12.10:*:*:*:*:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:13.10:*:*:*:*:*:*:*
    • cpe:2.3:o:canonical:ubuntu_linux:14.04:*:*:*:lts:*:*:*

Patches

3
c1a8c420fe4b

[1.4.x] Fixed a remote code execution vulnerabilty in URL reversing.

https://github.com/django/djangoTim GrahamApr 20, 2014via ghsa
5 files changed · +51 2
  • django/core/urlresolvers.py+21 1 modified
    @@ -230,6 +230,10 @@ def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, name
             self._reverse_dict = {}
             self._namespace_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):
             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 @@ def _populate(self):
             apps = {}
             language_code = get_language()
             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
                 if p_pattern.startswith('^'):
                     p_pattern = p_pattern[1:]
    @@ -260,6 +273,7 @@ def _populate(self):
                             namespaces[namespace] = (p_pattern + prefix, sub_pattern)
                         for app_name, namespace_list in pattern.app_dict.items():
                             apps.setdefault(app_name, []).extend(namespace_list)
    +                    self._callback_strs.update(pattern._callback_strs)
                 else:
                     bits = normalize(p_pattern)
                     lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
    @@ -268,6 +282,7 @@ def _populate(self):
             self._reverse_dict[language_code] = lookups
             self._namespace_dict[language_code] = namespaces
             self._app_dict[language_code] = apps
    +        self._populated = True
     
         @property
         def reverse_dict(self):
    @@ -356,8 +371,13 @@ def reverse(self, lookup_view, *args, **kwargs):
         def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
             if args and kwargs:
                 raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
    +
    +        if not self._populated:
    +            self._populate()
    +
             try:
    -            lookup_view = get_callable(lookup_view, True)
    +            if lookup_view in self._callback_strs:
    +                lookup_view = get_callable(lookup_view, True)
             except (ImportError, AttributeError), e:
                 raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
             possibilities = self.reverse_dict.getlist(lookup_view)
    
  • tests/regressiontests/urlpatterns_reverse/nonimported_module.py+3 0 added
    @@ -0,0 +1,3 @@
    +def view(request):
    +    """Stub view"""
    +    pass
    
  • tests/regressiontests/urlpatterns_reverse/tests.py+22 1 modified
    @@ -1,8 +1,11 @@
    +# -*- coding: utf-8 -*-
     """
     Unit tests for reverse URL lookups.
     """
     from __future__ import absolute_import
     
    +import sys
    +
     from django.conf import settings
     from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
     from django.core.urlresolvers import (reverse, resolve, NoReverseMatch,
    @@ -267,6 +270,25 @@ def test_redirect_to_url(self):
             self.assertEqual(res['Location'], '/foo/')
             res = redirect('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):
             from .views import absolute_kwargs_view
    @@ -510,4 +532,3 @@ def test_erroneous_resolve(self):
             self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_inner/')
             self.assertRaises(ViewDoesNotExist, self.client.get, '/missing_outer/')
             self.assertRaises(ViewDoesNotExist, self.client.get, '/uncallable/')
    -
    
  • tests/regressiontests/urlpatterns_reverse/urls.py+1 0 modified
    @@ -7,6 +7,7 @@
     
     other_patterns = patterns('',
         url(r'non_path_include/$', empty_view, name='non_path_include'),
    +    url(r'nested_path/$', 'regressiontests.urlpatterns_reverse.views.nested_view'),
     )
     
     urlpatterns = patterns('',
    
  • tests/regressiontests/urlpatterns_reverse/views.py+4 0 modified
    @@ -16,6 +16,10 @@ def absolute_kwargs_view(request, arg1=1, arg2=2):
     def defaults_view(request, arg1, arg2):
         pass
     
    +def nested_view(request):
    +    pass
    +
    +
     def erroneous_view(request):
         import non_existent
     
    
2a5bcb69f42b

[1.5.x] Fixed a remote code execution vulnerabilty in URL reversing.

https://github.com/django/djangoTim GrahamApr 20, 2014via ghsa
5 files changed · +51 2
  • django/core/urlresolvers.py+21 1 modified
    @@ -244,6 +244,10 @@ def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, name
             self._reverse_dict = {}
             self._namespace_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):
             if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
    @@ -261,6 +265,15 @@ def _populate(self):
             apps = {}
             language_code = get_language()
             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
                 if p_pattern.startswith('^'):
                     p_pattern = p_pattern[1:]
    @@ -281,6 +294,7 @@ def _populate(self):
                             namespaces[namespace] = (p_pattern + prefix, sub_pattern)
                         for app_name, namespace_list in pattern.app_dict.items():
                             apps.setdefault(app_name, []).extend(namespace_list)
    +                    self._callback_strs.update(pattern._callback_strs)
                 else:
                     bits = normalize(p_pattern)
                     lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
    @@ -289,6 +303,7 @@ def _populate(self):
             self._reverse_dict[language_code] = lookups
             self._namespace_dict[language_code] = namespaces
             self._app_dict[language_code] = apps
    +        self._populated = True
     
         @property
         def reverse_dict(self):
    @@ -375,8 +390,13 @@ def reverse(self, lookup_view, *args, **kwargs):
         def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
             if args and kwargs:
                 raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
    +
    +        if not self._populated:
    +            self._populate()
    +
             try:
    -            lookup_view = get_callable(lookup_view, True)
    +            if lookup_view in self._callback_strs:
    +                lookup_view = get_callable(lookup_view, True)
             except (ImportError, AttributeError) as e:
                 raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
             possibilities = self.reverse_dict.getlist(lookup_view)
    
  • tests/regressiontests/urlpatterns_reverse/nonimported_module.py+3 0 added
    @@ -0,0 +1,3 @@
    +def view(request):
    +    """Stub view"""
    +    pass
    
  • tests/regressiontests/urlpatterns_reverse/tests.py+22 1 modified
    @@ -1,8 +1,11 @@
    +# -*- coding: utf-8 -*-
     """
     Unit tests for reverse URL lookups.
     """
     from __future__ import absolute_import, unicode_literals
     
    +import sys
    +
     from django.conf import settings
     from django.contrib.auth.models import User
     from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
    @@ -290,6 +293,25 @@ def test_redirect_to_url(self):
             self.assertEqual(res['Location'], '/foo/')
             res = redirect('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):
             from .views import absolute_kwargs_view
    @@ -559,4 +581,3 @@ def test_view_loading(self):
             # swallow it.
             self.assertRaises(AttributeError, get_callable,
                 'regressiontests.urlpatterns_reverse.views_broken.i_am_broken')
    -
    
  • tests/regressiontests/urlpatterns_reverse/urls.py+1 0 modified
    @@ -7,6 +7,7 @@
     
     other_patterns = patterns('',
         url(r'non_path_include/$', empty_view, name='non_path_include'),
    +    url(r'nested_path/$', 'regressiontests.urlpatterns_reverse.views.nested_view'),
     )
     
     urlpatterns = patterns('',
    
  • tests/regressiontests/urlpatterns_reverse/views.py+4 0 modified
    @@ -16,6 +16,10 @@ def absolute_kwargs_view(request, arg1=1, arg2=2):
     def defaults_view(request, arg1, arg2):
         pass
     
    +def nested_view(request):
    +    pass
    +
    +
     def erroneous_view(request):
         import non_existent
     
    
4352a50871e2

[1.6.x] Fixed a remote code execution vulnerabilty in URL reversing.

https://github.com/django/djangoTim GrahamApr 20, 2014via ghsa
5 files changed · +50 2
  • django/core/urlresolvers.py+20 1 modified
    @@ -243,6 +243,10 @@ def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, name
             self._reverse_dict = {}
             self._namespace_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):
             if isinstance(self.urlconf_name, list) and len(self.urlconf_name):
    @@ -260,6 +264,15 @@ def _populate(self):
             apps = {}
             language_code = get_language()
             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
                 if p_pattern.startswith('^'):
                     p_pattern = p_pattern[1:]
    @@ -280,6 +293,7 @@ def _populate(self):
                             namespaces[namespace] = (p_pattern + prefix, sub_pattern)
                         for app_name, namespace_list in pattern.app_dict.items():
                             apps.setdefault(app_name, []).extend(namespace_list)
    +                    self._callback_strs.update(pattern._callback_strs)
                 else:
                     bits = normalize(p_pattern)
                     lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
    @@ -288,6 +302,7 @@ def _populate(self):
             self._reverse_dict[language_code] = lookups
             self._namespace_dict[language_code] = namespaces
             self._app_dict[language_code] = apps
    +        self._populated = True
     
         @property
         def reverse_dict(self):
    @@ -380,8 +395,12 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
             text_args = [force_text(v) for v in args]
             text_kwargs = dict((k, force_text(v)) for (k, v) in kwargs.items())
     
    +        if not self._populated:
    +            self._populate()
    +
             try:
    -            lookup_view = get_callable(lookup_view, True)
    +            if lookup_view in self._callback_strs:
    +                lookup_view = get_callable(lookup_view, True)
             except (ImportError, AttributeError) as e:
                 raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e))
             possibilities = self.reverse_dict.getlist(lookup_view)
    
  • tests/urlpatterns_reverse/nonimported_module.py+3 0 added
    @@ -0,0 +1,3 @@
    +def view(request):
    +    """Stub view"""
    +    pass
    
  • tests/urlpatterns_reverse/tests.py+22 1 modified
    @@ -1,8 +1,11 @@
    +# -*- coding: utf-8 -*-
     """
     Unit tests for reverse URL lookups.
     """
     from __future__ import absolute_import, unicode_literals
     
    +import sys
    +
     from django.conf import settings
     from django.contrib.auth.models import User
     from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
    @@ -313,6 +316,25 @@ def test_redirect_to_url(self):
             self.assertEqual(res.url, '/foo/')
             res = redirect('http://example.com/')
             self.assertEqual(res.url, 'http://example.com/')
    +        # Assert that we can redirect using UTF-8 strings
    +        res = redirect('/æøå/abc/')
    +        self.assertEqual(res.url, '/%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.url, '/%C3%A6%C3%B8%C3%A5.abc/')
    +        res = redirect('os.path')
    +        self.assertEqual(res.url, '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('urlpatterns_reverse.views.nested_view'), '/includes/nested_path/')
     
         def test_redirect_view_object(self):
             from .views import absolute_kwargs_view
    @@ -641,4 +663,3 @@ def test_view_loading(self):
             # swallow it.
             self.assertRaises(AttributeError, get_callable,
                 'urlpatterns_reverse.views_broken.i_am_broken')
    -
    
  • tests/urlpatterns_reverse/urls.py+1 0 modified
    @@ -7,6 +7,7 @@
     
     other_patterns = patterns('',
         url(r'non_path_include/$', empty_view, name='non_path_include'),
    +    url(r'nested_path/$', 'urlpatterns_reverse.views.nested_view'),
     )
     
     urlpatterns = patterns('',
    
  • tests/urlpatterns_reverse/views.py+4 0 modified
    @@ -16,6 +16,10 @@ def absolute_kwargs_view(request, arg1=1, arg2=2):
     def defaults_view(request, arg1, arg2):
         pass
     
    +def nested_view(request):
    +    pass
    +
    +
     def erroneous_view(request):
         import non_existent
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

14

News mentions

0

No linked articles in our index yet.