CVE-2020-35518: Observable Discrepancy (CWE-203)
value: 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.
CWE 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.
CVSS v3 base score: 5.3
Published: 2021-03-26T17:15Z
Repository: https://github.com/389ds/389-ds-base
Language: C
Files changed: dirsrvtests/tests/suites/basic/basic_test.py, ldap/servers/slapd/back-ldbm/ldbm_config.c, ldap/servers/slapd/result.c
Commit date: 2020-12-16 16:30:28 +0100
Commit message: 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
Diff: diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py
index 19b6d5dc6..7aaa42343 100644
--- a/dirsrvtests/tests/suites/basic/basic_test.py
+++ b/dirsrvtests/tests/suites/basic/basic_test.py
@@ -1475,10 +1475,40 @@ def test_dscreate_with_different_rdn(dscreate_test_rdn_value):
         if  dscreate_test_rdn_value[2] != "wrong=some_value":
             assert False
         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
     # -s for DEBUG mode
     CURRENT_FILE = os.path.realpath(__file__)
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c
index 84fdc3c34..3b56d6aa8 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c
@@ -1241,11 +1241,11 @@ ldbm_config_search_entry_callback(Slapi_PBlock *pb __attribute__((unused)),
             char **attrs = NULL;
             slapi_pblock_get(pb, SLAPI_SEARCH_ATTRS, &attrs);
             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;
                     }
                 }
             }
         
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
index 9daf3b151..ab0d79454 100644
--- a/ldap/servers/slapd/result.c
+++ b/ldap/servers/slapd/result.c
@@ -353,11 +353,11 @@ send_ldap_result_ext(
     slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
 
     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) {
         slapi_log_err(SLAPI_LOG_ERR, "send_ldap_result_ext", "No operation found: slapi_search_internal_set_pb was incomplete (invalid 'base' ?)\n");
         return;

Vulnerable code:                         slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, "at least one required attribute has been moved to the BDB scecific configuration entry");
        slapi_pblock_get(pb, SLAPI_PB_RESULT_TEXT, &pbtext);
Fixed code: 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')
                        slapi_pblock_set(pb, SLAPI_RESULT_TEXT, "at least one required attribute has been moved to the BDB scecific configuration entry");
        slapi_pblock_get(pb, SLAPI_RESULT_TEXT, &pbtext);