Security releases issued

Posted by James Bennett on Sept. 9, 2011

Today the Django team is issuing multiple releases -- Django 1.2.6 and Django 1.3.1 -- to remedy security issues reported to us. Additionally, this announcement contains advisories for several other issues which, while not requiring changes to Django itself, will be of concern to users of Django.

All users are encouraged to upgrade Django, and to implement the recommendations in these advisories, immediately.

Session manipulation

Django's session framework, django.contrib.sessions, is configurable to use any of multiple backends for storage of session data. One such backend, provided with Django itself, integrates with Django's cache framework to use the cache as storage for session data.

When configured in this fashion using memory-based sessions and caching, Django sessions are stored directly in the root namespace of the cache, using session identifiers as keys.

This results in a potential attack when coupled with an application storing user-supplied data in the cache; if an attacker can cause data to be cached using a key which is also a valid session identifier, Django's session framework will treat that data -- so long as it is a dictionary-like object -- as the session, thus allowing arbitrary data to be inserted into a session so long as the attacker knows the session key.

To mitigate this, the keys used to store sessions will now be namespaced in the cache, rather than being stored in the root namespace of the cache. Note that although this will invalidate current sessions for any site which deploys the updated sessions code, we do not consider this a backwards incompatibility, as session data is meant to be transient.

Denial of service attack via URLField

Django's model system includes a field type -- URLField -- which validates that the supplied value is a valid URL, and if the boolean keyword argument verify_exists is true, attempts to validate that the supplied URL also resolves, by issuing a request to it.

By default, the underlying socket libraries in Python do not have a timeout. This can manifest as a security problem in three different ways:

  1. An attacker can supply a slow-to-respond URL. Each request will tie up a server process for a period of time; if the attacker is able to make enough requests, they can tie up all available server processes.
  2. An attacker can supply a URL under his or her control, and which will simply hold an open connection indefinitely. Due to the lack of timeout, the Django process attempting to verify the URL will similarly spin indefinitely. Repeating this can easily tie up all available server processes.
  3. An attacker can supply a URL under his or her control which not only keeps the connection open, but also sends an unending stream of random garbage data. This data will cause the memory usage of the Django process (which will hold the response in memory) to grow without bound, thus consuming not only server processes but also server memory.

To resolve this, URLField will be modified in the following ways: the verify_exists argument will be defaulted to a false value; for more recent Python versions which support setting a timeout, a timeout of ten seconds will be set; verify_exists will be deprecated and ultimately removed from a future version of Django, as its utility is insufficient to warrant the potential risks it creates.

URLField redirection

The regular expression which validates URLs is used to check the supplied URL before issuing a check to verify that it exists, but if that URL issues a redirect in response to the request, no validation of the resulting redirected URL is performed, including basic checks for supported protocols (HTTP, HTTPS and FTP).

This creates a small window for an attacker to gain knowledge of, for example, server layout; a redirect to a file:// URL, for example, will tell an attacker whether a given file exists locally on the server.

Additionally, although the initial request issued by Django uses the HEAD method for HTTP/HTTPS, the request to the target of the redirect is issued using GET. This may create further issues for systems which implicitly trust GET requests from the local machine/network.

This issue is ultimately rooted in a bug in Python itself, which has been fixed, but as that fix does not yet appear to be in wide use, we will be modifying Django's URL-checking functions -- for their remaining lifetime before removal due to deprecation -- to use a whitelist of allowed protocols for redirects, and to treat a redirect as a URL which exists, but not to follow the redirect.

Host header cache poisoning

In several places, Django itself -- independent of the developer -- generates full URLs (for example, when issuing HTTP redirects). Currently this uses the value of the HTTP Host header from the request to construct the URL, which opens a potential cache-poisoning vector: an attacker can submit a request with a Host header of his or her choice, receive a response which constructs URLs using that Host header, and -- if that response is cached -- further requests will be served out of cache using URLs containing the attacker's host of choice.

To resolve this, Django now ignores the X-Forwarded-Host header by default when constructing full URLs. If your site is served behind a proxy that you are confident sets and validates X-Forwarded-Host correctly, you can enable it via the USE_X_FORWARDED_HOST setting. However, both Django itself and user-developed code will also be vulnerable to this style of attack unless the suggestions below -- in the advisory labeled "Host header and CSRF" -- are applied.

Advisory: Host header and CSRF

In certain web server configurations, it is possible for an attacker to bypass Django's CSRF-protection mechanism. The attack works as follows:

  1. The attacker hosts a page at attacker.com, and sets a CNAME for the subdomain subdomain.attacker.com, pointing to the targeted site, victim.com.
  2. Visitors to attacker.com are presented with a site that uses JavaScript to send requests to subdomain.attacker.com. This will be permitted by the same-origin policy, and the attacker will be able to control the cookies and data sent with these requests, including providing a CSRF cookie and a token for POST requests.
  3. Due to the CNAME, requests to subdomain.attacker.com be sent to victim.com. If the victim's web server is not validating incoming HTTP Host headers (or has a catch-all virtual host responding to any request not matching a specific named virtual host), it will then pass that request on to Django, and Django's CSRF mechanism will allow the request, due to the presence of the CSRF cookie and token.

This does not -- so far as we are aware -- allow the types of malicious data-manipulation or escalation of privileges typically associated with a CSRF hole (since the attacker cannot access or manipulate cookies/sessions for the victim site), but is still considered a CSRF hole.

To avoid this potential attack, we recommend that users of Django ensure their web-server configuration always validates incoming HTTP Host headers against the expected host name, disallows requests with no Host header, and that the web server not be configured with a catch-all virtual host which forwards requests to a Django application.

Advisory: Cross-subdomain CSRF attacks

Due to the nature of HTTP cookies, a cookie can be set from a subdomain which will be valid for the entire domain. This means that an attacker who controls a subdomain can, for example, set a CSRF cookie which will be valid for the entire domain.

This is not a bug in Django itself, but is a potential vector for attack that users of Django should be aware of; to avoid this attack, ensure that subdomains of your domain are only under the control of trusted users (which is a standard best practice). Some notes on this are included in Django's CSRF documentation.

Advisory: DEBUG pages and sensitive POST data

When the DEBUG setting is True, Django will handle exceptions by generating a nicely-formatted error page, including the full traceback and a display of the HTTP request and relevant settings.

Sensitive settings -- such as passwords -- are already obscured in this display, but by default data submitted with the HTTP request is not. Thus, for example, an error in a login view could result in a DEBUG page displaying the plain-text password (from the POST data).

To mitigate this, the development version of Django adds two decorators which will obscure specific information in debugging reports. Users whose applications handle sensitive data are advised to read the documentation for these decorators and use them appropriately.

Affected versions

All of the issues described above are present in the following versions of Django:

Resolution

Patches have been applied to Django trunk, and to the 1.3 and 1.2 release branches, which resolve the three security issues described above. The patches may be obtained directly from the following changesets:

The following new releases have been issued:

As Django trunk is currently in a pre-alpha state, users are strongly advised not to be running production deployments from it; if you are currently doing so, however, you are urged to upgrade immediately to the latest trunk, which contains the above patches.

Credits

The first three security issues above were all reported to us by Paul McMillan, who -- due to his prolific work on identifying and helping to mitigate security issues in Django -- has now been added to the Django core development team. The patches for the above issues were also developed primarily by Paul.

The Host-header cache-poisoning vulnerability was discovered by the core team, through an article reporting the possibility of such attacks.

The cross-subdomain CSRF report was originally supplied by Mozilla.

The issue of sensitive data displaying in DEBUG pages, and the mechanism for mitigating this, both came courtesy of Julien Phalip, who has also recently joined the Django core team.

General notes regarding security

As always, we ask that potential security issues be reported via private email to security@djangoproject.com, and not via Django's Trac instance or the django-developers list.

If you are or represent a third-party distributor of Django and did not receive a notification email regarding this announcement from the Django release manager, please contact james@b-list.org.

Back to Top