|
|
058656 |
From 40fcaabfaa2c865471cc5fb1fab04106bc3ec611 Mon Sep 17 00:00:00 2001
|
|
|
058656 |
From: William Brown <firstyear@redhat.com>
|
|
|
058656 |
Date: Thu, 18 Jan 2018 11:27:58 +1000
|
|
|
058656 |
Subject: [PATCH] Ticket bz1525628 - invalid password migration causes unauth
|
|
|
058656 |
bind
|
|
|
058656 |
|
|
|
058656 |
Bug Description: Slapi_ct_memcmp expects both inputs to be
|
|
|
058656 |
at LEAST size n. If they are not, we only compared UP to n.
|
|
|
058656 |
|
|
|
058656 |
Invalid migrations of passwords (IE {CRYPT}XX) would create
|
|
|
058656 |
a pw which is just salt and no hash. ct_memcmp would then
|
|
|
058656 |
only verify the salt bits and would allow the authentication.
|
|
|
058656 |
|
|
|
058656 |
This relies on an administrative mistake both of allowing
|
|
|
058656 |
password migration (nsslapd-allow-hashed-passwords) and then
|
|
|
058656 |
subsequently migrating an INVALID password to the server.
|
|
|
058656 |
|
|
|
058656 |
Fix Description: slapi_ct_memcmp now access n1, n2 size
|
|
|
058656 |
and will FAIL if they are not the same, but will still compare
|
|
|
058656 |
n bytes, where n is the "longest" memory, to the first byte
|
|
|
058656 |
of the other to prevent length disclosure of the shorter
|
|
|
058656 |
value (generally the mis-migrated password)
|
|
|
058656 |
|
|
|
058656 |
https://bugzilla.redhat.com/show_bug.cgi?id=1525628
|
|
|
058656 |
|
|
|
058656 |
Author: wibrown
|
|
|
058656 |
|
|
|
058656 |
Review by: ???
|
|
|
058656 |
|
|
|
058656 |
Signed-off-by: Mark Reynolds <mreynolds@redhat.com>
|
|
|
058656 |
---
|
|
|
058656 |
.../bz1525628_ct_memcmp_invalid_hash_test.py | 56 ++++++++++++++++++++++
|
|
|
058656 |
ldap/servers/plugins/pwdstorage/clear_pwd.c | 4 +-
|
|
|
058656 |
ldap/servers/plugins/pwdstorage/crypt_pwd.c | 4 +-
|
|
|
058656 |
ldap/servers/plugins/pwdstorage/md5_pwd.c | 4 +-
|
|
|
058656 |
ldap/servers/plugins/pwdstorage/sha_pwd.c | 16 +++++--
|
|
|
058656 |
ldap/servers/plugins/pwdstorage/smd5_pwd.c | 2 +-
|
|
|
058656 |
ldap/servers/slapd/ch_malloc.c | 36 ++++++++++++--
|
|
|
058656 |
ldap/servers/slapd/slapi-plugin.h | 2 +-
|
|
|
058656 |
8 files changed, 108 insertions(+), 16 deletions(-)
|
|
|
058656 |
create mode 100644 dirsrvtests/tests/suites/password/bz1525628_ct_memcmp_invalid_hash_test.py
|
|
|
058656 |
|
|
|
058656 |
diff --git a/dirsrvtests/tests/suites/password/bz1525628_ct_memcmp_invalid_hash_test.py b/dirsrvtests/tests/suites/password/bz1525628_ct_memcmp_invalid_hash_test.py
|
|
|
058656 |
new file mode 100644
|
|
|
058656 |
index 000000000..2f38384a1
|
|
|
058656 |
--- /dev/null
|
|
|
058656 |
+++ b/dirsrvtests/tests/suites/password/bz1525628_ct_memcmp_invalid_hash_test.py
|
|
|
058656 |
@@ -0,0 +1,56 @@
|
|
|
058656 |
+# --- BEGIN COPYRIGHT BLOCK ---
|
|
|
058656 |
+# Copyright (C) 2018 Red Hat, Inc.
|
|
|
058656 |
+# All rights reserved.
|
|
|
058656 |
+#
|
|
|
058656 |
+# License: GPL (version 3 or any later version).
|
|
|
058656 |
+# See LICENSE for details.
|
|
|
058656 |
+# --- END COPYRIGHT BLOCK ---
|
|
|
058656 |
+#
|
|
|
058656 |
+
|
|
|
058656 |
+import ldap
|
|
|
058656 |
+import pytest
|
|
|
058656 |
+import logging
|
|
|
058656 |
+from lib389.topologies import topology_st
|
|
|
058656 |
+from lib389._constants import PASSWORD, DEFAULT_SUFFIX
|
|
|
058656 |
+
|
|
|
058656 |
+from lib389.idm.user import UserAccounts, TEST_USER_PROPERTIES
|
|
|
058656 |
+
|
|
|
058656 |
+logging.getLogger(__name__).setLevel(logging.DEBUG)
|
|
|
058656 |
+log = logging.getLogger(__name__)
|
|
|
058656 |
+
|
|
|
058656 |
+def test_invalid_hash_fails(topology_st):
|
|
|
058656 |
+ """When given a malformed hash from userpassword migration
|
|
|
058656 |
+ slapi_ct_memcmp would check only to the length of the shorter
|
|
|
058656 |
+ field. This affects some values where it would ONLY verify
|
|
|
058656 |
+ the salt is valid, and thus would allow any password to bind.
|
|
|
058656 |
+
|
|
|
058656 |
+ :id: 8131c029-7147-47db-8d03-ec5db2a01cfb
|
|
|
058656 |
+ :setup: Standalone Instance
|
|
|
058656 |
+ :steps:
|
|
|
058656 |
+ 1. Create a user
|
|
|
058656 |
+ 2. Add an invalid password hash (truncated)
|
|
|
058656 |
+ 3. Attempt to bind
|
|
|
058656 |
+ :expectedresults:
|
|
|
058656 |
+ 1. User is added
|
|
|
058656 |
+ 2. Invalid pw hash is added
|
|
|
058656 |
+ 3. Bind fails
|
|
|
058656 |
+ """
|
|
|
058656 |
+ log.info("Running invalid hash test")
|
|
|
058656 |
+
|
|
|
058656 |
+ # Allow setting raw password hashes for migration.
|
|
|
058656 |
+ topology_st.standalone.config.set('nsslapd-allow-hashed-passwords', 'on')
|
|
|
058656 |
+
|
|
|
058656 |
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
|
|
058656 |
+ user = users.create(properties=TEST_USER_PROPERTIES)
|
|
|
058656 |
+ user.set('userPassword', '{CRYPT}XX')
|
|
|
058656 |
+
|
|
|
058656 |
+ # Attempt to bind. This should fail.
|
|
|
058656 |
+ with pytest.raises(ldap.INVALID_CREDENTIALS):
|
|
|
058656 |
+ user.bind(PASSWORD)
|
|
|
058656 |
+ with pytest.raises(ldap.INVALID_CREDENTIALS):
|
|
|
058656 |
+ user.bind('XX')
|
|
|
058656 |
+ with pytest.raises(ldap.INVALID_CREDENTIALS):
|
|
|
058656 |
+ user.bind('{CRYPT}XX')
|
|
|
058656 |
+
|
|
|
058656 |
+ log.info("PASSED")
|
|
|
058656 |
+
|
|
|
058656 |
diff --git a/ldap/servers/plugins/pwdstorage/clear_pwd.c b/ldap/servers/plugins/pwdstorage/clear_pwd.c
|
|
|
058656 |
index f5e6f9d4c..3d340752d 100644
|
|
|
058656 |
--- a/ldap/servers/plugins/pwdstorage/clear_pwd.c
|
|
|
058656 |
+++ b/ldap/servers/plugins/pwdstorage/clear_pwd.c
|
|
|
058656 |
@@ -39,7 +39,7 @@ clear_pw_cmp(const char *userpwd, const char *dbpwd)
|
|
|
058656 |
* However, even if the first part of userpw matches dbpwd, but len !=, we
|
|
|
058656 |
* have already failed anyawy. This prevents substring matching.
|
|
|
058656 |
*/
|
|
|
058656 |
- if (slapi_ct_memcmp(userpwd, dbpwd, len_dbp) != 0) {
|
|
|
058656 |
+ if (slapi_ct_memcmp(userpwd, dbpwd, len_user, len_dbp) != 0) {
|
|
|
058656 |
result = 1;
|
|
|
058656 |
}
|
|
|
058656 |
} else {
|
|
|
058656 |
@@ -51,7 +51,7 @@ clear_pw_cmp(const char *userpwd, const char *dbpwd)
|
|
|
058656 |
* dbpwd to itself. We have already got result == 1 if we are here, so we are
|
|
|
058656 |
* just trying to take up time!
|
|
|
058656 |
*/
|
|
|
058656 |
- if (slapi_ct_memcmp(dbpwd, dbpwd, len_dbp)) {
|
|
|
058656 |
+ if (slapi_ct_memcmp(dbpwd, dbpwd, len_dbp, len_dbp)) {
|
|
|
058656 |
/* Do nothing, we have the if to fix a coverity check. */
|
|
|
058656 |
}
|
|
|
058656 |
}
|
|
|
058656 |
diff --git a/ldap/servers/plugins/pwdstorage/crypt_pwd.c b/ldap/servers/plugins/pwdstorage/crypt_pwd.c
|
|
|
058656 |
index 3bd226581..0dccd1b51 100644
|
|
|
058656 |
--- a/ldap/servers/plugins/pwdstorage/crypt_pwd.c
|
|
|
058656 |
+++ b/ldap/servers/plugins/pwdstorage/crypt_pwd.c
|
|
|
058656 |
@@ -65,13 +65,13 @@ crypt_close(Slapi_PBlock *pb __attribute__((unused)))
|
|
|
058656 |
int
|
|
|
058656 |
crypt_pw_cmp(const char *userpwd, const char *dbpwd)
|
|
|
058656 |
{
|
|
|
058656 |
- int rc;
|
|
|
058656 |
+ int32_t rc;
|
|
|
058656 |
char *cp;
|
|
|
058656 |
PR_Lock(cryptlock);
|
|
|
058656 |
/* we use salt (first 2 chars) of encoded password in call to crypt() */
|
|
|
058656 |
cp = crypt(userpwd, dbpwd);
|
|
|
058656 |
if (cp) {
|
|
|
058656 |
- rc = slapi_ct_memcmp(dbpwd, cp, strlen(dbpwd));
|
|
|
058656 |
+ rc = slapi_ct_memcmp(dbpwd, cp, strlen(dbpwd), strlen(cp));
|
|
|
058656 |
} else {
|
|
|
058656 |
rc = -1;
|
|
|
058656 |
}
|
|
|
058656 |
diff --git a/ldap/servers/plugins/pwdstorage/md5_pwd.c b/ldap/servers/plugins/pwdstorage/md5_pwd.c
|
|
|
058656 |
index 1e2cf58e7..2c2aacaa6 100644
|
|
|
058656 |
--- a/ldap/servers/plugins/pwdstorage/md5_pwd.c
|
|
|
058656 |
+++ b/ldap/servers/plugins/pwdstorage/md5_pwd.c
|
|
|
058656 |
@@ -30,7 +30,7 @@
|
|
|
058656 |
int
|
|
|
058656 |
md5_pw_cmp(const char *userpwd, const char *dbpwd)
|
|
|
058656 |
{
|
|
|
058656 |
- int rc = -1;
|
|
|
058656 |
+ int32_t rc = -1;
|
|
|
058656 |
char *bver;
|
|
|
058656 |
PK11Context *ctx = NULL;
|
|
|
058656 |
unsigned int outLen;
|
|
|
058656 |
@@ -57,7 +57,7 @@ md5_pw_cmp(const char *userpwd, const char *dbpwd)
|
|
|
058656 |
bver = NSSBase64_EncodeItem(NULL, (char *)b2a_out, sizeof b2a_out, &binary_item);
|
|
|
058656 |
/* bver points to b2a_out upon success */
|
|
|
058656 |
if (bver) {
|
|
|
058656 |
- rc = slapi_ct_memcmp(bver, dbpwd, strlen(dbpwd));
|
|
|
058656 |
+ rc = slapi_ct_memcmp(bver, dbpwd, strlen(dbpwd), strlen(bver));
|
|
|
058656 |
} else {
|
|
|
058656 |
slapi_log_err(SLAPI_LOG_PLUGIN, MD5_SUBSYSTEM_NAME,
|
|
|
058656 |
"Could not base64 encode hashed value for password compare");
|
|
|
058656 |
diff --git a/ldap/servers/plugins/pwdstorage/sha_pwd.c b/ldap/servers/plugins/pwdstorage/sha_pwd.c
|
|
|
058656 |
index 1fbe0bc82..381b31d7c 100644
|
|
|
058656 |
--- a/ldap/servers/plugins/pwdstorage/sha_pwd.c
|
|
|
058656 |
+++ b/ldap/servers/plugins/pwdstorage/sha_pwd.c
|
|
|
058656 |
@@ -49,7 +49,7 @@ sha_pw_cmp(const char *userpwd, const char *dbpwd, unsigned int shaLen)
|
|
|
058656 |
char userhash[MAX_SHA_HASH_SIZE];
|
|
|
058656 |
char quick_dbhash[MAX_SHA_HASH_SIZE + SHA_SALT_LENGTH + 3];
|
|
|
058656 |
char *dbhash = quick_dbhash;
|
|
|
058656 |
- struct berval salt;
|
|
|
058656 |
+ struct berval salt = {0};
|
|
|
058656 |
PRUint32 hash_len;
|
|
|
058656 |
unsigned int secOID;
|
|
|
058656 |
char *schemeName;
|
|
|
058656 |
@@ -122,9 +122,19 @@ sha_pw_cmp(const char *userpwd, const char *dbpwd, unsigned int shaLen)
|
|
|
058656 |
|
|
|
058656 |
/* the proof is in the comparison... */
|
|
|
058656 |
if (hash_len >= shaLen) {
|
|
|
058656 |
- result = slapi_ct_memcmp(userhash, dbhash, shaLen);
|
|
|
058656 |
+ /*
|
|
|
058656 |
+ * This say "if the hash has a salt IE >, OR if they are equal, check the hash component ONLY.
|
|
|
058656 |
+ * This is why we repeat shaLen twice, even though it seems odd. If you have a dbhast of ssha
|
|
|
058656 |
+ * it's len is 28, and the userpw is 20, but 0 - 20 is the sha, and 21-28 is the salt, which
|
|
|
058656 |
+ * has already been processed into userhash.
|
|
|
058656 |
+ * The case where dbpwd is truncated is handled above in "invalid base64" arm.
|
|
|
058656 |
+ */
|
|
|
058656 |
+ result = slapi_ct_memcmp(userhash, dbhash, shaLen, shaLen);
|
|
|
058656 |
} else {
|
|
|
058656 |
- result = slapi_ct_memcmp(userhash, dbhash + OLD_SALT_LENGTH, hash_len - OLD_SALT_LENGTH);
|
|
|
058656 |
+ /* This case is for if the salt is at the START, which only applies to DS40B1 case.
|
|
|
058656 |
+ * May never be a valid check...
|
|
|
058656 |
+ */
|
|
|
058656 |
+ result = slapi_ct_memcmp(userhash, dbhash + OLD_SALT_LENGTH, shaLen, hash_len - OLD_SALT_LENGTH);
|
|
|
058656 |
}
|
|
|
058656 |
|
|
|
058656 |
loser:
|
|
|
058656 |
diff --git a/ldap/servers/plugins/pwdstorage/smd5_pwd.c b/ldap/servers/plugins/pwdstorage/smd5_pwd.c
|
|
|
058656 |
index a83ac6fa4..cbfc74ff3 100644
|
|
|
058656 |
--- a/ldap/servers/plugins/pwdstorage/smd5_pwd.c
|
|
|
058656 |
+++ b/ldap/servers/plugins/pwdstorage/smd5_pwd.c
|
|
|
058656 |
@@ -82,7 +82,7 @@ smd5_pw_cmp(const char *userpwd, const char *dbpwd)
|
|
|
058656 |
PK11_DestroyContext(ctx, 1);
|
|
|
058656 |
|
|
|
058656 |
/* Compare everything up to the salt. */
|
|
|
058656 |
- rc = slapi_ct_memcmp(userhash, dbhash, MD5_LENGTH);
|
|
|
058656 |
+ rc = slapi_ct_memcmp(userhash, dbhash, MD5_LENGTH, MD5_LENGTH);
|
|
|
058656 |
|
|
|
058656 |
loser:
|
|
|
058656 |
if (dbhash && dbhash != quick_dbhash)
|
|
|
058656 |
diff --git a/ldap/servers/slapd/ch_malloc.c b/ldap/servers/slapd/ch_malloc.c
|
|
|
058656 |
index ef436b3e8..90a2b2c1a 100644
|
|
|
058656 |
--- a/ldap/servers/slapd/ch_malloc.c
|
|
|
058656 |
+++ b/ldap/servers/slapd/ch_malloc.c
|
|
|
058656 |
@@ -336,8 +336,8 @@ slapi_ch_smprintf(const char *fmt, ...)
|
|
|
058656 |
|
|
|
058656 |
/* Constant time memcmp. Does not shortcircuit on failure! */
|
|
|
058656 |
/* This relies on p1 and p2 both being size at least n! */
|
|
|
058656 |
-int
|
|
|
058656 |
-slapi_ct_memcmp(const void *p1, const void *p2, size_t n)
|
|
|
058656 |
+int32_t
|
|
|
058656 |
+slapi_ct_memcmp(const void *p1, const void *p2, size_t n1, size_t n2)
|
|
|
058656 |
{
|
|
|
058656 |
int result = 0;
|
|
|
058656 |
const unsigned char *_p1 = (const unsigned char *)p1;
|
|
|
058656 |
@@ -347,9 +347,35 @@ slapi_ct_memcmp(const void *p1, const void *p2, size_t n)
|
|
|
058656 |
return 2;
|
|
|
058656 |
}
|
|
|
058656 |
|
|
|
058656 |
- for (size_t i = 0; i < n; i++) {
|
|
|
058656 |
- if (_p1[i] ^ _p2[i]) {
|
|
|
058656 |
- result = 1;
|
|
|
058656 |
+ if (n1 == n2) {
|
|
|
058656 |
+ for (size_t i = 0; i < n1; i++) {
|
|
|
058656 |
+ if (_p1[i] ^ _p2[i]) {
|
|
|
058656 |
+ result = 1;
|
|
|
058656 |
+ }
|
|
|
058656 |
+ }
|
|
|
058656 |
+ } else {
|
|
|
058656 |
+ const unsigned char *_pa;
|
|
|
058656 |
+ const unsigned char *_pb;
|
|
|
058656 |
+ size_t nl;
|
|
|
058656 |
+ if (n2 > n1) {
|
|
|
058656 |
+ _pa = _p2;
|
|
|
058656 |
+ _pb = _p2;
|
|
|
058656 |
+ nl = n2;
|
|
|
058656 |
+ } else {
|
|
|
058656 |
+ _pa = _p1;
|
|
|
058656 |
+ _pb = _p1;
|
|
|
058656 |
+ nl = n1;
|
|
|
058656 |
+ }
|
|
|
058656 |
+ /* We already fail as n1 != n2 */
|
|
|
058656 |
+ result = 3;
|
|
|
058656 |
+ for (size_t i = 0; i < nl; i++) {
|
|
|
058656 |
+ if (_pa[i] ^ _pb[i]) {
|
|
|
058656 |
+ /*
|
|
|
058656 |
+ * If we don't mutate result here, dead code elimination
|
|
|
058656 |
+ * we remove for loop.
|
|
|
058656 |
+ */
|
|
|
058656 |
+ result = 4;
|
|
|
058656 |
+ }
|
|
|
058656 |
}
|
|
|
058656 |
}
|
|
|
058656 |
return result;
|
|
|
058656 |
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
|
|
|
058656 |
index 4566202d3..95cdcc0da 100644
|
|
|
058656 |
--- a/ldap/servers/slapd/slapi-plugin.h
|
|
|
058656 |
+++ b/ldap/servers/slapd/slapi-plugin.h
|
|
|
058656 |
@@ -5862,7 +5862,7 @@ char *slapi_ch_smprintf(const char *fmt, ...)
|
|
|
058656 |
* \param n length in bytes of the content of p1 AND p2.
|
|
|
058656 |
* \return 0 on match. 1 on non-match. 2 on presence of NULL pointer in p1 or p2.
|
|
|
058656 |
*/
|
|
|
058656 |
-int slapi_ct_memcmp(const void *p1, const void *p2, size_t n);
|
|
|
058656 |
+int32_t slapi_ct_memcmp(const void *p1, const void *p2, size_t n1, size_t n2);
|
|
|
058656 |
|
|
|
058656 |
/*
|
|
|
058656 |
* syntax plugin routines
|
|
|
058656 |
--
|
|
|
058656 |
2.13.6
|
|
|
058656 |
|