From 5b09354954348f1a56df32b2d7dbe074a23a4532 Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Tue, 25 Feb 2020 15:16:19 +0200 Subject: [PATCH] Fixed #31291 -- Renamed salt to mask for CSRF tokens. --- django/middleware/csrf.py | 40 +++++++++++----------- docs/ref/csrf.txt | 6 ++-- tests/csrf_tests/test_context_processor.py | 2 +- tests/csrf_tests/tests.py | 2 +- tests/template_backends/test_dummy.py | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index 77e3f042d2..4ac2f23019 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -42,33 +42,33 @@ def _get_new_csrf_string(): return get_random_string(CSRF_SECRET_LENGTH, allowed_chars=CSRF_ALLOWED_CHARS) -def _salt_cipher_secret(secret): +def _mask_cipher_secret(secret): """ Given a secret (assumed to be a string of CSRF_ALLOWED_CHARS), generate a - token by adding a salt and using it to encrypt the secret. + token by adding a mask and applying it to the secret. """ - salt = _get_new_csrf_string() + mask = _get_new_csrf_string() chars = CSRF_ALLOWED_CHARS - pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in salt)) + pairs = zip((chars.index(x) for x in secret), (chars.index(x) for x in mask)) cipher = ''.join(chars[(x + y) % len(chars)] for x, y in pairs) - return salt + cipher + return mask + cipher -def _unsalt_cipher_token(token): +def _unmask_cipher_token(token): """ Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length - CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt + CSRF_TOKEN_LENGTH, and that its first half is a mask), use it to decrypt the second half to produce the original secret. """ - salt = token[:CSRF_SECRET_LENGTH] + mask = token[:CSRF_SECRET_LENGTH] token = token[CSRF_SECRET_LENGTH:] chars = CSRF_ALLOWED_CHARS - pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt)) + pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in mask)) return ''.join(chars[x - y] for x, y in pairs) # Note negative values are ok def _get_new_csrf_token(): - return _salt_cipher_secret(_get_new_csrf_string()) + return _mask_cipher_secret(_get_new_csrf_string()) def get_token(request): @@ -83,11 +83,11 @@ def get_token(request): """ if "CSRF_COOKIE" not in request.META: csrf_secret = _get_new_csrf_string() - request.META["CSRF_COOKIE"] = _salt_cipher_secret(csrf_secret) + request.META["CSRF_COOKIE"] = _mask_cipher_secret(csrf_secret) else: - csrf_secret = _unsalt_cipher_token(request.META["CSRF_COOKIE"]) + csrf_secret = _unmask_cipher_token(request.META["CSRF_COOKIE"]) request.META["CSRF_COOKIE_USED"] = True - return _salt_cipher_secret(csrf_secret) + return _mask_cipher_secret(csrf_secret) def rotate_token(request): @@ -111,20 +111,20 @@ def _sanitize_token(token): elif len(token) == CSRF_SECRET_LENGTH: # Older Django versions set cookies to values of CSRF_SECRET_LENGTH # alphanumeric characters. For backwards compatibility, accept - # such values as unsalted secrets. - # It's easier to salt here and be consistent later, rather than add + # such values as unmasked secrets. + # It's easier to mask here and be consistent later, rather than add # different code paths in the checks, although that might be a tad more # efficient. - return _salt_cipher_secret(token) + return _mask_cipher_secret(token) return _get_new_csrf_token() -def _compare_salted_tokens(request_csrf_token, csrf_token): +def _compare_masked_tokens(request_csrf_token, csrf_token): # Assume both arguments are sanitized -- that is, strings of # length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS. return constant_time_compare( - _unsalt_cipher_token(request_csrf_token), - _unsalt_cipher_token(csrf_token), + _unmask_cipher_token(request_csrf_token), + _unmask_cipher_token(csrf_token), ) @@ -306,7 +306,7 @@ class CsrfViewMiddleware(MiddlewareMixin): request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '') request_csrf_token = _sanitize_token(request_csrf_token) - if not _compare_salted_tokens(request_csrf_token, csrf_token): + if not _compare_masked_tokens(request_csrf_token, csrf_token): return self._reject(request, REASON_BAD_TOKEN) return self._accept(request) diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt index 72277415af..7e2a5d45d0 100644 --- a/docs/ref/csrf.txt +++ b/docs/ref/csrf.txt @@ -239,15 +239,15 @@ The CSRF protection is based on the following things: set on the request. 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. + secret; a random mask 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. #. A hidden form field with the name 'csrfmiddlewaretoken' present in all 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 + secret, with a mask which is both added to it and used to scramble it. The + mask 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. diff --git a/tests/csrf_tests/test_context_processor.py b/tests/csrf_tests/test_context_processor.py index 62e4365cd8..62bde2d085 100644 --- a/tests/csrf_tests/test_context_processor.py +++ b/tests/csrf_tests/test_context_processor.py @@ -1,5 +1,5 @@ from django.http import HttpRequest -from django.middleware.csrf import _compare_salted_tokens as equivalent_tokens +from django.middleware.csrf import _compare_masked_tokens as equivalent_tokens from django.template.context_processors import csrf from django.test import SimpleTestCase diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py index 0a55cc307e..63cbd08c94 100644 --- a/tests/csrf_tests/tests.py +++ b/tests/csrf_tests/tests.py @@ -7,7 +7,7 @@ from django.http import HttpRequest, HttpResponse from django.middleware.csrf import ( CSRF_SESSION_KEY, CSRF_TOKEN_LENGTH, REASON_BAD_TOKEN, REASON_NO_CSRF_COOKIE, CsrfViewMiddleware, - _compare_salted_tokens as equivalent_tokens, get_token, + _compare_masked_tokens as equivalent_tokens, get_token, ) from django.test import SimpleTestCase, override_settings from django.views.decorators.csrf import csrf_exempt, requires_csrf_token diff --git a/tests/template_backends/test_dummy.py b/tests/template_backends/test_dummy.py index 5790c0604a..598397a711 100644 --- a/tests/template_backends/test_dummy.py +++ b/tests/template_backends/test_dummy.py @@ -3,7 +3,7 @@ import re from django.forms import CharField, Form, Media from django.http import HttpRequest, HttpResponse from django.middleware.csrf import ( - CsrfViewMiddleware, _compare_salted_tokens as equivalent_tokens, get_token, + CsrfViewMiddleware, _compare_masked_tokens as equivalent_tokens, get_token, ) from django.template import TemplateDoesNotExist, TemplateSyntaxError from django.template.backends.dummy import TemplateStrings