CVE-2020-35518 (published 2021-03-26T17:15Z)

When binding against a DN during authentication, the reply from 389-ds-base will be different whether the DN exists or not. This can be used by an unauthenticated attacker to check the existence of an entry in the LDAP database.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:P/I:N/A:N)
CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)

Weakness information:
cwe_id: CWE-203
cwe_name: Observable Discrepancy
description: The product behaves differently or sends different responses under different circumstances in a way that is observable to an unauthorized actor, which exposes security-relevant information about the state of the product, such as whether a particular operation was successful or not.
extended_description: Discrepancies can take many forms, and variations may be detectable in timing, control flow, communications such as replies or requests, or general behavior. These discrepancies can reveal information about the product's operation or internal state to an unauthorized actor. In some cases, discrepancies can be used by attackers to form a side channel.
         

Fix information:
repo_url: https://github.com/389ds/389-ds-base
commit_details: repo_url: https://github.com/389ds/389-ds-base
author: tbordaz
author_date: 2020-12-16 16:30:28+01:00
msg: Issue 4480 - Unexpected info returned to ldap request (#4491)

Bug description:
	If the bind entry does not exist, the bind result info
        reports that 'No such entry'. It should not give any
        information if the target entry exists or not

Fix description:
	Does not return any additional information during a bind

relates: https://github.com/389ds/389-ds-base/issues/4480

Reviewed by: William Brown, Viktor Ashirov, Mark Reynolds (thank you all)

Platforms tested:  F31
repository: repo_url: https://github.com/389ds/389-ds-base
repo_name: 389ds/389-ds-base
description: The enterprise-class Open Source LDAP server for Linux
repo_language: C
owner: 389ds
file_changes: filename: basic_test.py
change_type: ModificationType.MODIFY
diff: @@ -1477,6 +1477,36 @@ def test_dscreate_with_different_rdn(dscreate_test_rdn_value):
         else:
             assert True
 
+def test_bind_invalid_entry(topology_st):
+    """Test the failing bind does not return information about the entry
+
+    :id: 5cd9b083-eea6-426b-84ca-83c26fc49a6f
+
+    :setup: Standalone instance
+
+    :steps:
+    1: bind as non existing entry
+    2: check that bind info does not report 'No such entry'
+
+    :expectedresults:
+    1: pass
+    2: pass
+    """
+
+    topology_st.standalone.restart()
+    INVALID_ENTRY="cn=foooo,%s" % DEFAULT_SUFFIX
+    try:
+        topology_st.standalone.simple_bind_s(INVALID_ENTRY, PASSWORD)
+    except ldap.LDAPError as e:
+        log.info('test_bind_invalid_entry: Failed to bind as %s (expected)' % INVALID_ENTRY)
+        log.info('exception description: ' + e.args[0]['desc'])
+        if 'info' in e.args[0]:
+            log.info('exception info: ' + e.args[0]['info'])
+        assert e.args[0]['desc'] == 'Invalid credentials'
+        assert 'info' not in e.args[0]
+        pass
+
+    log.info('test_bind_invalid_entry: PASSED')
 
 if __name__ == '__main__':
     # Run isolated

programming_language: Python
filename: ldbm_config.c
change_type: ModificationType.MODIFY
diff: @@ -1243,7 +1243,7 @@ ldbm_config_search_entry_callback(Slapi_PBlock *pb __attribute__((unused)),
             if (attrs) {
                 for (size_t i = 0; attrs[i]; i++) {
                     if (ldbm_config_moved_attr(attrs[i])) {
-                        slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "at least one required attribute has been moved to the BDB scecific configuration entry");
+                        slapi_pblock_set(pb, SLAPI_RESULT_TEXT, "at least one required attribute has been moved to the BDB scecific configuration entry");
                         break;
                     }
                 }

programming_language: C
filename: result.c
change_type: ModificationType.MODIFY
diff: @@ -355,7 +355,7 @@ send_ldap_result_ext(
     if (text) {
         pbtext = text;
     } else {
-        slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &pbtext);
+        slapi_pblock_get(pb, SLAPI_RESULT_TEXT, &pbtext);
     }
 
     if (operation == NULL) {

programming_language: C
repo_url: https://github.com/389ds/389-ds-base
commit_details: repo_url: https://github.com/389ds/389-ds-base
author: Mark Reynolds
author_date: 2021-02-09 14:02:59-05:00
msg: Issue 4609 - CVE - info disclosure when authenticating

Description:  If you bind as a user that does not exist.  Error 49 is returned
              instead of error 32.  As error 32 discloses that the entry does
              not exist.  When you bind as an entry that does not have userpassword
              set then error 48 (inappropriate auth) is returned, but this
              discloses that the entry does indeed exist.  Instead we should
              always return error 49, even if the password is not set in the
              entry.  This way we do not disclose to an attacker if the Bind
              DN exists or not.

Relates: https://github.com/389ds/389-ds-base/issues/4609

Reviewed by: tbordaz(Thanks!)
repository: repo_url: https://github.com/389ds/389-ds-base
repo_name: 389ds/389-ds-base
description: The enterprise-class Open Source LDAP server for Linux
repo_language: C
owner: 389ds
file_changes: filename: basic_test.py
change_type: ModificationType.MODIFY
diff: @@ -9,7 +9,7 @@
 
 from subprocess import check_output, PIPE, run
 from lib389 import DirSrv
-from lib389.idm.user import UserAccounts
+from lib389.idm.user import UserAccount, UserAccounts
 import pytest
 from lib389.tasks import *
 from lib389.utils import *
@@ -1063,18 +1063,14 @@ def test_bind_invalid_entry(topology_st):
     """Test the failing bind does not return information about the entry
 
     :id: 5cd9b083-eea6-426b-84ca-83c26fc49a6f
-
     :customerscenario: True
-
     :setup: Standalone instance
-
     :steps:
-    1: bind as non existing entry
-    2: check that bind info does not report 'No such entry'
-
+        1: bind as non existing entry
+        2: check that bind info does not report 'No such entry'
     :expectedresults:
-    1: pass
-    2: pass
+        1: pass
+        2: pass
     """
 
     topology_st.standalone.restart()
@@ -1096,6 +1092,43 @@ def test_bind_invalid_entry(topology_st):
     topology_st.standalone.simple_bind_s(DN_DM, PW_DM)
 
 
+def test_bind_entry_missing_passwd(topology_st):
+    """
+    :id: af209149-8fb8-48cb-93ea-3e82dd7119d2
+    :setup: Standalone Instance
+    :steps:
+        1. Bind as database entry that does not have userpassword set
+        2. Bind as database entry that does not exist
+        1. Bind as cn=config entry that does not have userpassword set
+        2. Bind as cn=config entry that does not exist
+    :expectedresults:
+        1. Fails with error 49
+        2. Fails with error 49
+        3. Fails with error 49
+        4. Fails with error 49
+    """
+    user = UserAccount(topology_st.standalone, DEFAULT_SUFFIX)
+    with pytest.raises(ldap.INVALID_CREDENTIALS):
+        # Bind as the suffix root entry which does not have a userpassword
+        user.bind("some_password")
+
+    user = UserAccount(topology_st.standalone, "cn=not here," + DEFAULT_SUFFIX)
+    with pytest.raises(ldap.INVALID_CREDENTIALS):
+        # Bind as the entry which does not exist
+        user.bind("some_password")
+
+    # Test cn=config since it has its own code path
+    user = UserAccount(topology_st.standalone, "cn=config")
+    with pytest.raises(ldap.INVALID_CREDENTIALS):
+        # Bind as the config entry which does not have a userpassword
+        user.bind("some_password")
+
+    user = UserAccount(topology_st.standalone, "cn=does not exist,cn=config")
+    with pytest.raises(ldap.INVALID_CREDENTIALS):
+        # Bind as an entry under cn=config that does not exist
+        user.bind("some_password")
+
+
 @pytest.mark.bz1044135
 @pytest.mark.ds47319
 def test_connection_buffer_size(topology_st):

programming_language: Python
filename: ldbm_bind.c
change_type: ModificationType.MODIFY
diff: @@ -76,8 +76,8 @@ ldbm_back_bind(Slapi_PBlock *pb)
     case LDAP_AUTH_SIMPLE: {
         Slapi_Value cv;
         if (slapi_entry_attr_find(e->ep_entry, "userpassword", &attr) != 0) {
-            slapi_send_ldap_result(pb, LDAP_INAPPROPRIATE_AUTH, NULL,
-                                   NULL, 0, NULL);
+            slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "Entry does not have userpassword set");
+            slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, NULL, 0, NULL);
             CACHE_RETURN(&inst->inst_cache, &e);
             rc = SLAPI_BIND_FAIL;
             goto bail;

programming_language: C
filename: dse.c
change_type: ModificationType.MODIFY
diff: @@ -1446,7 +1446,8 @@ dse_bind(Slapi_PBlock *pb) /* JCM There should only be one exit point from this
 
     ec = dse_get_entry_copy(pdse, sdn, DSE_USE_LOCK);
     if (ec == NULL) {
-        slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL);
+        slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "Entry does not exist");
+        slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, NULL, 0, NULL);
         return (SLAPI_BIND_FAIL);
     }
 
@@ -1454,7 +1455,8 @@ dse_bind(Slapi_PBlock *pb) /* JCM There should only be one exit point from this
     case LDAP_AUTH_SIMPLE: {
         Slapi_Value cv;
         if (slapi_entry_attr_find(ec, "userpassword", &attr) != 0) {
-            slapi_send_ldap_result(pb, LDAP_INAPPROPRIATE_AUTH, NULL, NULL, 0, NULL);
+            slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "Entry does not have userpassword set");
+            slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, NULL, 0, NULL);
             slapi_entry_free(ec);
             return SLAPI_BIND_FAIL;
         }
@@ -1462,6 +1464,7 @@ dse_bind(Slapi_PBlock *pb) /* JCM There should only be one exit point from this
 
         slapi_value_init_berval(&cv, cred);
         if (slapi_pw_find_sv(bvals, &cv) != 0) {
+            slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "Invalid credentials");
             slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, NULL, NULL, 0, NULL);
             slapi_entry_free(ec);
             value_done(&cv);

programming_language: C