mirror of
https://github.com/django/django.git
synced 2025-07-24 05:36:15 +00:00
Fixed #20869 -- made CSRF tokens change every request by salt-encrypting them
Note that the cookie is not changed every request, just the token retrieved by the `get_token()` method (used also by the `{% csrf_token %}` tag). While at it, made token validation strict: Where, before, any length was accepted and non-ASCII chars were ignored, we now treat anything other than `[A-Za-z0-9]{64}` as invalid (except for 32-char tokens, which, for backwards-compatibility, are accepted and replaced by 64-char ones). Thanks Trac user patrys for reporting, github user adambrenecki for initial patch, Tim Graham for help, and Curtis Maloney, Collin Anderson, Florian Apolloner, Markus Holtermann & Jon Dufresne for reviews.
This commit is contained in:
parent
6d9c5d46e6
commit
5112e65ef2
8 changed files with 241 additions and 70 deletions
|
@ -218,20 +218,25 @@ How it works
|
|||
|
||||
The CSRF protection is based on the following things:
|
||||
|
||||
1. A CSRF cookie that is set to a random value (a session independent nonce, as
|
||||
it is called), which other sites will not have access to.
|
||||
1. A CSRF cookie that is based on a random secret value, which other sites
|
||||
will not have access to.
|
||||
|
||||
This cookie is set by ``CsrfViewMiddleware``. It is meant to be permanent,
|
||||
but since there is no way to set a cookie that never expires, it is sent with
|
||||
every response that has called ``django.middleware.csrf.get_token()``
|
||||
(the function used internally to retrieve the CSRF token).
|
||||
This cookie is set by ``CsrfViewMiddleware``. It is sent with every
|
||||
response that has called ``django.middleware.csrf.get_token()`` (the
|
||||
function used internally to retrieve the CSRF token), if it wasn't already
|
||||
set on the request.
|
||||
|
||||
For security reasons, the value of the CSRF cookie is changed each time a
|
||||
In order to protect against `BREACH`_ attacks, the token is not simply the
|
||||
secret; a random salt is prepended to the secret and used to scramble it.
|
||||
|
||||
For security reasons, the value of the secret is changed each time a
|
||||
user logs in.
|
||||
|
||||
2. A hidden form field with the name 'csrfmiddlewaretoken' present in all
|
||||
outgoing POST forms. The value of this field is the value of the CSRF
|
||||
cookie.
|
||||
outgoing POST forms. The value of this field is, again, the value of the
|
||||
secret, with a salt which is both added to it and used to scramble it. The
|
||||
salt is regenerated on every call to ``get_token()`` so that the form field
|
||||
value is changed in every such response.
|
||||
|
||||
This part is done by the template tag.
|
||||
|
||||
|
@ -239,6 +244,11 @@ The CSRF protection is based on the following things:
|
|||
TRACE, a CSRF cookie must be present, and the 'csrfmiddlewaretoken' field
|
||||
must be present and correct. If it isn't, the user will get a 403 error.
|
||||
|
||||
When validating the 'csrfmiddlewaretoken' field value, only the secret,
|
||||
not the full token, is compared with the secret in the cookie value.
|
||||
This allows the use of ever-changing tokens. While each request may use its
|
||||
own token, the secret remains common to all.
|
||||
|
||||
This check is done by ``CsrfViewMiddleware``.
|
||||
|
||||
4. In addition, for HTTPS requests, strict referer checking is done by
|
||||
|
@ -247,7 +257,7 @@ The CSRF protection is based on the following things:
|
|||
application since that request won't come from your own exact domain.
|
||||
|
||||
This also addresses a man-in-the-middle attack that's possible under HTTPS
|
||||
when using a session independent nonce, due to the fact that HTTP
|
||||
when using a session independent secret, due to the fact that HTTP
|
||||
``Set-Cookie`` headers are (unfortunately) accepted by clients even when
|
||||
they are talking to a site under HTTPS. (Referer checking is not done for
|
||||
HTTP requests because the presence of the ``Referer`` header isn't reliable
|
||||
|
@ -283,6 +293,13 @@ vulnerability allows and much worse).
|
|||
|
||||
Checking against the :setting:`CSRF_COOKIE_DOMAIN` setting was added.
|
||||
|
||||
.. versionchanged:: 1.10
|
||||
|
||||
Added salting to the token and started changing it with each request
|
||||
to protect against `BREACH`_ attacks.
|
||||
|
||||
.. _BREACH: http://breachattack.com/
|
||||
|
||||
Caching
|
||||
=======
|
||||
|
||||
|
@ -499,15 +516,6 @@ No, this is by design. Not linking CSRF protection to a session allows using
|
|||
the protection on sites such as a `pastebin` that allow submissions from
|
||||
anonymous users which don't have a session.
|
||||
|
||||
Why not use a new token for each request?
|
||||
-----------------------------------------
|
||||
|
||||
Generating a new token for each request is problematic from a UI perspective
|
||||
because it invalidates all previous forms. Most users would be very unhappy to
|
||||
find that opening a new tab on your site has invalidated the form they had
|
||||
just spent time filling out in another tab or that a form they accessed via
|
||||
the back button could not be filled out.
|
||||
|
||||
Why might a user encounter a CSRF validation failure after logging in?
|
||||
----------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -118,13 +118,12 @@ GZip middleware
|
|||
.. warning::
|
||||
|
||||
Security researchers recently revealed that when compression techniques
|
||||
(including ``GZipMiddleware``) are used on a website, the site becomes
|
||||
exposed to a number of possible attacks. These approaches can be used to
|
||||
compromise, among other things, Django's CSRF protection. Before using
|
||||
``GZipMiddleware`` on your site, you should consider very carefully whether
|
||||
you are subject to these attacks. If you're in *any* doubt about whether
|
||||
you're affected, you should avoid using ``GZipMiddleware``. For more
|
||||
details, see the `the BREACH paper (PDF)`_ and `breachattack.com`_.
|
||||
(including ``GZipMiddleware``) are used on a website, the site may become
|
||||
exposed to a number of possible attacks. Before using ``GZipMiddleware`` on
|
||||
your site, you should consider very carefully whether you are subject to
|
||||
these attacks. If you're in *any* doubt about whether you're affected, you
|
||||
should avoid using ``GZipMiddleware``. For more details, see the `the BREACH
|
||||
paper (PDF)`_ and `breachattack.com`_.
|
||||
|
||||
.. _the BREACH paper (PDF): http://breachattack.com/resources/BREACH%20-%20SSL,%20gone%20in%2030%20seconds.pdf
|
||||
.. _breachattack.com: http://breachattack.com
|
||||
|
@ -147,6 +146,12 @@ It will NOT compress content if any of the following are true:
|
|||
You can apply GZip compression to individual views using the
|
||||
:func:`~django.views.decorators.gzip.gzip_page()` decorator.
|
||||
|
||||
.. versionchanged:: 1.10
|
||||
|
||||
In older versions, Django's CSRF protection mechanism was vulnerable to
|
||||
BREACH attacks when compression was used. This is no longer the case, but
|
||||
you should still take care not to compromise your own secrets this way.
|
||||
|
||||
Conditional GET middleware
|
||||
--------------------------
|
||||
|
||||
|
|
|
@ -256,6 +256,12 @@ CSRF
|
|||
accepts an optional ``template_name`` parameter, defaulting to
|
||||
``'403_csrf.html'``, to control the template used to render the page.
|
||||
|
||||
* To protect against `BREACH`_ attacks, the CSRF protection mechanism now
|
||||
changes the form token value on every request (while keeping an invariant
|
||||
secret which can be used to validate the different tokens).
|
||||
|
||||
.. _BREACH: http://breachattack.com/
|
||||
|
||||
Database backends
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -795,6 +801,12 @@ Miscellaneous
|
|||
* ``utils.version.get_version()`` returns :pep:`440` compliant release
|
||||
candidate versions (e.g. '1.10rc1' instead of '1.10c1').
|
||||
|
||||
* CSRF token values are now required to be strings of 64 alphanumerics; values
|
||||
of 32 alphanumerics, as set by older versions of Django by default, are
|
||||
automatically replaced by strings of 64 characters. Other values are
|
||||
considered invalid. This should only affect developers or users who replace
|
||||
these tokens.
|
||||
|
||||
* The ``LOGOUT_URL`` setting is removed as Django hasn't made use of it
|
||||
since pre-1.0. If you use it in your project, you can add it to your
|
||||
project's settings. The default value was ``'/accounts/logout/'``.
|
||||
|
|
|
@ -65,10 +65,10 @@ this if you know what you are doing. There are other :ref:`limitations
|
|||
<csrf-limitations>` if your site has subdomains that are outside of your
|
||||
control.
|
||||
|
||||
:ref:`CSRF protection works <how-csrf-works>` by checking for a nonce in each
|
||||
:ref:`CSRF protection works <how-csrf-works>` by checking for a secret in each
|
||||
POST request. This ensures that a malicious user cannot simply "replay" a form
|
||||
POST to your website and have another logged in user unwittingly submit that
|
||||
form. The malicious user would have to know the nonce, which is user specific
|
||||
form. The malicious user would have to know the secret, which is user specific
|
||||
(using a cookie).
|
||||
|
||||
When deployed with :ref:`HTTPS <security-recommendation-ssl>`,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue