Security releases issued

Posted by James Bennett on September 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:

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.

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.

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:

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.

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.

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 stil
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).

Affected versions

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

Django development trunk

Django 1.3

Django 1.2

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:

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 imediately 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.