andykimpe / rpms / 389-ds-base

Forked from rpms/389-ds-base 5 months ago
Clone
22b3c5
From c16a4211a5f8f3a8dc1568af11116717e6a8aed7 Mon Sep 17 00:00:00 2001
22b3c5
From: Firstyear <william@blackhats.net.au>
22b3c5
Date: Fri, 9 Jul 2021 11:53:35 +1000
22b3c5
Subject: [PATCH] Issue 4817 - BUG - locked crypt accounts on import may allow
22b3c5
 all passwords (#4819)
22b3c5
22b3c5
Bug Description: Due to mishanding of short dbpwd hashes, the
22b3c5
crypt_r algorithm was misused and was only comparing salts
22b3c5
in some cases, rather than checking the actual content
22b3c5
of the password.
22b3c5
22b3c5
Fix Description: Stricter checks on dbpwd lengths to ensure
22b3c5
that content passed to crypt_r has at least 2 salt bytes and
22b3c5
1 hash byte, as well as stricter checks on ct_memcmp to ensure
22b3c5
that compared values are the same length, rather than potentially
22b3c5
allowing overruns/short comparisons.
22b3c5
22b3c5
fixes: https://github.com/389ds/389-ds-base/issues/4817
22b3c5
22b3c5
Author: William Brown <william@blackhats.net.au>
22b3c5
22b3c5
Review by: @mreynolds389
22b3c5
---
22b3c5
 .../password/pwd_crypt_asterisk_test.py       | 50 +++++++++++++++++++
22b3c5
 ldap/servers/plugins/pwdstorage/crypt_pwd.c   | 20 +++++---
22b3c5
 2 files changed, 64 insertions(+), 6 deletions(-)
22b3c5
 create mode 100644 dirsrvtests/tests/suites/password/pwd_crypt_asterisk_test.py
22b3c5
22b3c5
diff --git a/dirsrvtests/tests/suites/password/pwd_crypt_asterisk_test.py b/dirsrvtests/tests/suites/password/pwd_crypt_asterisk_test.py
22b3c5
new file mode 100644
22b3c5
index 000000000..d76614db1
22b3c5
--- /dev/null
22b3c5
+++ b/dirsrvtests/tests/suites/password/pwd_crypt_asterisk_test.py
22b3c5
@@ -0,0 +1,50 @@
22b3c5
+# --- BEGIN COPYRIGHT BLOCK ---
22b3c5
+# Copyright (C) 2021 William Brown <william@blackhats.net.au>
22b3c5
+# All rights reserved.
22b3c5
+#
22b3c5
+# License: GPL (version 3 or any later version).
22b3c5
+# See LICENSE for details.
22b3c5
+# --- END COPYRIGHT BLOCK ---
22b3c5
+#
22b3c5
+import ldap
22b3c5
+import pytest
22b3c5
+from lib389.topologies import topology_st
22b3c5
+from lib389.idm.user import UserAccounts
22b3c5
+from lib389._constants import (DEFAULT_SUFFIX, PASSWORD)
22b3c5
+
22b3c5
+pytestmark = pytest.mark.tier1
22b3c5
+
22b3c5
+def test_password_crypt_asterisk_is_rejected(topology_st):
22b3c5
+    """It was reported that {CRYPT}* was allowing all passwords to be
22b3c5
+    valid in the bind process. This checks that we should be rejecting
22b3c5
+    these as they should represent locked accounts. Similar, {CRYPT}!
22b3c5
+
22b3c5
+    :id: 0b8f1a6a-f3eb-4443-985e-da14d0939dc3
22b3c5
+    :setup: Single instance
22b3c5
+    :steps: 1. Set a password hash in with CRYPT and the content *
22b3c5
+            2. Test a bind
22b3c5
+            3. Set a password hash in with CRYPT and the content !
22b3c5
+            4. Test a bind
22b3c5
+    :expectedresults:
22b3c5
+            1. Successfully set the values
22b3c5
+            2. The bind fails
22b3c5
+            3. Successfully set the values
22b3c5
+            4. The bind fails
22b3c5
+    """
22b3c5
+    topology_st.standalone.config.set('nsslapd-allow-hashed-passwords', 'on')
22b3c5
+    topology_st.standalone.config.set('nsslapd-enable-upgrade-hash', 'off')
22b3c5
+
22b3c5
+    users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
22b3c5
+    user = users.create_test_user()
22b3c5
+
22b3c5
+    user.set('userPassword', "{CRYPT}*")
22b3c5
+
22b3c5
+    # Attempt to bind with incorrect password.
22b3c5
+    with pytest.raises(ldap.INVALID_CREDENTIALS):
22b3c5
+        badconn = user.bind('badpassword')
22b3c5
+
22b3c5
+    user.set('userPassword', "{CRYPT}!")
22b3c5
+    # Attempt to bind with incorrect password.
22b3c5
+    with pytest.raises(ldap.INVALID_CREDENTIALS):
22b3c5
+        badconn = user.bind('badpassword')
22b3c5
+
22b3c5
diff --git a/ldap/servers/plugins/pwdstorage/crypt_pwd.c b/ldap/servers/plugins/pwdstorage/crypt_pwd.c
22b3c5
index a849d0075..2f2664393 100644
22b3c5
--- a/ldap/servers/plugins/pwdstorage/crypt_pwd.c
22b3c5
+++ b/ldap/servers/plugins/pwdstorage/crypt_pwd.c
22b3c5
@@ -36,15 +36,23 @@ static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */
22b3c5
 int
22b3c5
 crypt_pw_cmp(const char *userpwd, const char *dbpwd)
22b3c5
 {
22b3c5
-    int rc;
22b3c5
-    char *cp;
22b3c5
+    int rc = -1;
22b3c5
+    char *cp = NULL;
22b3c5
+    size_t dbpwd_len = strlen(dbpwd);
22b3c5
     struct crypt_data data;
22b3c5
     data.initialized = 0;
22b3c5
 
22b3c5
-    /* we use salt (first 2 chars) of encoded password in call to crypt_r() */
22b3c5
-    cp = crypt_r(userpwd, dbpwd, &data);
22b3c5
-    if (cp) {
22b3c5
-        rc = slapi_ct_memcmp(dbpwd, cp, strlen(dbpwd));
22b3c5
+    /*
22b3c5
+     * there MUST be at least 2 chars of salt and some pw bytes, else this is INVALID and will
22b3c5
+     * allow any password to bind as we then only compare SALTS.
22b3c5
+     */
22b3c5
+    if (dbpwd_len >= 3) {
22b3c5
+        /* we use salt (first 2 chars) of encoded password in call to crypt_r() */
22b3c5
+        cp = crypt_r(userpwd, dbpwd, &data);
22b3c5
+    }
22b3c5
+    /* If these are not the same length, we can not proceed safely with memcmp. */
22b3c5
+    if (cp && dbpwd_len == strlen(cp)) {
22b3c5
+        rc = slapi_ct_memcmp(dbpwd, cp, dbpwd_len);
22b3c5
     } else {
22b3c5
         rc = -1;
22b3c5
     }
22b3c5
-- 
22b3c5
2.31.1
22b3c5