1
0
mirror of https://github.com/django/django.git synced 2025-10-24 06:06:09 +00:00

Fixed CVE-2016-2513 -- Fixed user enumeration timing attack during login.

This is a security fix.
This commit is contained in:
Florian Apolloner
2016-02-13 21:09:46 +01:00
committed by Tim Graham
parent c5544d2892
commit 67b46ba701
5 changed files with 211 additions and 21 deletions

View File

@@ -10,9 +10,10 @@ from django.contrib.auth.hashers import (
check_password, get_hasher, identify_hasher, is_password_usable,
make_password,
)
from django.test import SimpleTestCase
from django.test import SimpleTestCase, mock
from django.test.utils import override_settings
from django.utils import six
from django.utils.encoding import force_bytes
try:
import crypt
@@ -214,6 +215,28 @@ class TestUtilsHashPass(SimpleTestCase):
finally:
hasher.rounds = old_rounds
@skipUnless(bcrypt, "bcrypt not installed")
def test_bcrypt_harden_runtime(self):
hasher = get_hasher('bcrypt')
self.assertEqual('bcrypt', hasher.algorithm)
with mock.patch.object(hasher, 'rounds', 4):
encoded = make_password('letmein', hasher='bcrypt')
with mock.patch.object(hasher, 'rounds', 6), \
mock.patch.object(hasher, 'encode', side_effect=hasher.encode):
hasher.harden_runtime('wrong_password', encoded)
# Increasing rounds from 4 to 6 means an increase of 4 in workload,
# therefore hardening should run 3 times to make the timing the
# same (the original encode() call already ran once).
self.assertEqual(hasher.encode.call_count, 3)
# Get the original salt (includes the original workload factor)
algorithm, data = encoded.split('$', 1)
expected_call = (('wrong_password', force_bytes(data[:29])),)
self.assertEqual(hasher.encode.call_args_list, [expected_call] * 3)
def test_unusable(self):
encoded = make_password(None)
self.assertEqual(len(encoded), len(UNUSABLE_PASSWORD_PREFIX) + UNUSABLE_PASSWORD_SUFFIX_LENGTH)
@@ -337,6 +360,25 @@ class TestUtilsHashPass(SimpleTestCase):
finally:
hasher.iterations = old_iterations
def test_pbkdf2_harden_runtime(self):
hasher = get_hasher('default')
self.assertEqual('pbkdf2_sha256', hasher.algorithm)
with mock.patch.object(hasher, 'iterations', 1):
encoded = make_password('letmein')
with mock.patch.object(hasher, 'iterations', 6), \
mock.patch.object(hasher, 'encode', side_effect=hasher.encode):
hasher.harden_runtime('wrong_password', encoded)
# Encode should get called once ...
self.assertEqual(hasher.encode.call_count, 1)
# ... with the original salt and 5 iterations.
algorithm, iterations, salt, hash = encoded.split('$', 3)
expected_call = (('wrong_password', salt, 5),)
self.assertEqual(hasher.encode.call_args, expected_call)
def test_pbkdf2_upgrade_new_hasher(self):
hasher = get_hasher('default')
self.assertEqual('pbkdf2_sha256', hasher.algorithm)
@@ -365,6 +407,20 @@ class TestUtilsHashPass(SimpleTestCase):
self.assertTrue(check_password('letmein', encoded, setter))
self.assertTrue(state['upgraded'])
def test_check_password_calls_harden_runtime(self):
hasher = get_hasher('default')
encoded = make_password('letmein')
with mock.patch.object(hasher, 'harden_runtime'), \
mock.patch.object(hasher, 'must_update', return_value=True):
# Correct password supplied, no hardening needed
check_password('letmein', encoded)
self.assertEqual(hasher.harden_runtime.call_count, 0)
# Wrong password supplied, hardening needed
check_password('wrong_password', encoded)
self.assertEqual(hasher.harden_runtime.call_count, 1)
def test_load_library_no_algorithm(self):
with self.assertRaises(ValueError) as e:
BasePasswordHasher()._load_library()