From 861f17d2cb50fc649feee004be1ce08d2e3873f8 Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Tue, 9 Feb 2021 14:02:59 -0500 Subject: [PATCH 4/4] 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!) --- dirsrvtests/tests/suites/basic/basic_test.py | 72 +++++++++++++++++++- ldap/servers/slapd/back-ldbm/ldbm_bind.c | 4 +- ldap/servers/slapd/dse.c | 7 +- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/dirsrvtests/tests/suites/basic/basic_test.py b/dirsrvtests/tests/suites/basic/basic_test.py index e9afa1e7e..6244782fa 100644 --- a/dirsrvtests/tests/suites/basic/basic_test.py +++ b/dirsrvtests/tests/suites/basic/basic_test.py @@ -13,7 +13,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 * @@ -1062,6 +1062,76 @@ def test_search_ou(topology_st): assert len(entries) == 0 +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' + :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') + + # reset credentials + 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): diff --git a/ldap/servers/slapd/back-ldbm/ldbm_bind.c b/ldap/servers/slapd/back-ldbm/ldbm_bind.c index fa450ecd5..38d115a32 100644 --- a/ldap/servers/slapd/back-ldbm/ldbm_bind.c +++ b/ldap/servers/slapd/back-ldbm/ldbm_bind.c @@ -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; diff --git a/ldap/servers/slapd/dse.c b/ldap/servers/slapd/dse.c index 0e22d3cec..0d3268046 100644 --- a/ldap/servers/slapd/dse.c +++ b/ldap/servers/slapd/dse.c @@ -1443,7 +1443,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); } @@ -1451,7 +1452,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; } @@ -1459,6 +1461,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); -- 2.26.2