From: Joseph Sutton Date: Wed, 9 Feb 2022 00:50:10 +0000 (+1300) Subject: tests/password_lockout: Test NTLM and SAMR password changes with Protected Users X-Git-Tag: tevent-0.12.0~374 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fd765aaa5b319ac997cb82b6fa296e6af2f67db9;p=thirdparty%2Fsamba.git tests/password_lockout: Test NTLM and SAMR password changes with Protected Users Test that NTLM and SAMR password changes cannot be used for Protected Users, and that lockouts are not triggered for attempting to use them. Signed-off-by: Joseph Sutton Reviewed-by: Stefan Metzmacher --- diff --git a/selftest/knownfail.d/protected_users b/selftest/knownfail.d/protected_users new file mode 100644 index 00000000000..c0370381264 --- /dev/null +++ b/selftest/knownfail.d/protected_users @@ -0,0 +1,3 @@ +^samba4.ldap.password_lockout.python\(ad_dc_slowtests\).__main__.PasswordTestsWithoutSleep.test_ntlm_lockout_protected.ad_dc_slowtests +^samba4.ldap.password_lockout.python\(ad_dc_slowtests\).__main__.PasswordTestsWithoutSleep.test_samr_change_password_protected.ad_dc_slowtests +^samba4.ldap.password_lockout.python\(ad_dc_slowtests\).__main__.PasswordTestsWithoutSleep.test_samr_set_password_protected.ad_dc_slowtests diff --git a/source4/dsdb/tests/python/password_lockout.py b/source4/dsdb/tests/python/password_lockout.py index 9aa23afd774..a1a0ae0e864 100755 --- a/source4/dsdb/tests/python/password_lockout.py +++ b/source4/dsdb/tests/python/password_lockout.py @@ -775,6 +775,283 @@ userPassword: thatsAcomplPASS2XYZ self._test_samr_password_change(self.lockout1ntlm_creds, other_creds=self.lockout2ntlm_creds) + def test_ntlm_lockout_protected(self): + creds = self.lockout1ntlm_creds + self.assertEqual(DONT_USE_KERBEROS, creds.get_kerberos_state()) + + # Work out the initial account values for this user. + username = creds.get_username() + userdn = f'cn={username},cn=users,{self.base_dn}' + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=('greater', 0), + badPwdCountOnly=True) + badPasswordTime = int(res[0]['badPasswordTime'][0]) + logonCount = int(res[0]['logonCount'][0]) + lastLogon = int(res[0]['lastLogon'][0]) + lastLogonTimestamp = int(res[0]['lastLogonTimestamp'][0]) + + # Add the user to the Protected Users group. + + # Search for the Protected Users group. + group_dn = Dn(self.ldb, + f'') + try: + group_res = self.ldb.search(base=group_dn, + scope=SCOPE_BASE, + attrs=['member']) + except LdbError as err: + self.fail(err) + + orig_msg = group_res[0] + + # Add the user to the list of members. + members = list(orig_msg.get('member', ())) + self.assertNotIn(userdn, members, 'account already in Protected Users') + members.append(userdn) + + m = Message(group_dn) + m['member'] = MessageElement(members, + FLAG_MOD_REPLACE, + 'member') + cleanup = self.ldb.msg_diff(m, orig_msg) + self.ldb.modify(m) + + password = creds.get_password() + creds.set_password('wrong_password') + + lockout_threshold = 5 + + lp = self.get_loadparm() + server = f'ldap://{self.ldb.host_dns_name()}' + + for _ in range(lockout_threshold): + with self.assertRaises(LdbError) as err: + SamDB(url=server, + credentials=creds, + lp=lp) + + num, _ = err.exception.args + self.assertEqual(ERR_INVALID_CREDENTIALS, num) + + res = self._check_account( + userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=None, + userAccountControl=dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + # The user should not be locked out. + self.assertNotIn('lockoutTime', res[0], + 'account unexpectedly locked out') + + # Move the account out of 'Protected Users'. + self.ldb.modify(cleanup) + + # The account should not be locked out. + creds.set_password(password) + + try: + SamDB(url=server, + credentials=creds, + lp=lp) + except LdbError: + self.fail('account unexpectedly locked out') + + def test_samr_change_password_protected(self): + """Tests the SAMR password change method for Protected Users""" + + creds = self.lockout1ntlm_creds + other_creds = self.lockout2ntlm_creds + lockout_threshold = 5 + + # Create a connection for SAMR using another user's credentials. + lp = self.get_loadparm() + net = Net(other_creds, lp, server=self.host) + + # Work out the initial account values for this user. + username = creds.get_username() + userdn = f'cn={username},cn=users,{self.base_dn}' + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=('greater', 0), + badPwdCountOnly=True) + badPasswordTime = int(res[0]['badPasswordTime'][0]) + logonCount = int(res[0]['logonCount'][0]) + lastLogon = int(res[0]['lastLogon'][0]) + lastLogonTimestamp = int(res[0]['lastLogonTimestamp'][0]) + + # prove we can change the user password (using the correct password) + new_password = 'thatsAcomplPASS1' + net.change_password(newpassword=new_password, + username=username, + oldpassword=creds.get_password()) + creds.set_password(new_password) + + # Add the user to the Protected Users group. + + # Search for the Protected Users group. + group_dn = Dn(self.ldb, + f'') + try: + group_res = self.ldb.search(base=group_dn, + scope=SCOPE_BASE, + attrs=['member']) + except LdbError as err: + self.fail(err) + + orig_msg = group_res[0] + + # Add the user to the list of members. + members = list(orig_msg.get('member', ())) + self.assertNotIn(userdn, members, 'account already in Protected Users') + members.append(userdn) + + m = Message(group_dn) + m['member'] = MessageElement(members, + FLAG_MOD_REPLACE, + 'member') + self.ldb.modify(m) + + # Try entering the correct password 'x' times in a row, which should + # fail, but not lock the user out. + new_password = 'thatsAcomplPASS2' + for i in range(lockout_threshold): + with self.assertRaises( + NTSTATUSError, + msg='Invalid SAMR change_password accepted') as err: + print(f'Trying correct password, attempt #{i}') + net.change_password(newpassword=new_password, + username=username, + oldpassword=creds.get_password()) + + enum = ctypes.c_uint32(err.exception.args[0]).value + self.assertEqual(enum, ntstatus.NT_STATUS_ACCOUNT_RESTRICTION) + + res = self._check_account( + userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=None, + userAccountControl=dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + # The user should not be locked out. + self.assertNotIn('lockoutTime', res[0]) + + # Ensure that the password can still be changed via LDAP. + self.ldb.modify_ldif(f''' +dn: {userdn} +changetype: modify +delete: userPassword +userPassword: {creds.get_password()} +add: userPassword +userPassword: {new_password} +''') + + def test_samr_set_password_protected(self): + """Tests the SAMR password set method for Protected Users""" + + creds = self.lockout1ntlm_creds + lockout_threshold = 5 + + # create a connection for SAMR using another user's credentials + lp = self.get_loadparm() + net = Net(self.global_creds, lp, server=self.host) + + # work out the initial account values for this user + username = creds.get_username() + userdn = f'cn={username},cn=users,{self.base_dn}' + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=('greater', 0), + badPwdCountOnly=True) + badPasswordTime = int(res[0]['badPasswordTime'][0]) + logonCount = int(res[0]['logonCount'][0]) + lastLogon = int(res[0]['lastLogon'][0]) + lastLogonTimestamp = int(res[0]['lastLogonTimestamp'][0]) + + # prove we can change the user password (using the correct password) + new_password = 'thatsAcomplPASS1' + net.set_password(newpassword=new_password, + account_name=username, + domain_name=creds.get_domain()) + creds.set_password(new_password) + + # Add the user to the Protected Users group. + + # Search for the Protected Users group. + group_dn = Dn(self.ldb, + f'') + try: + group_res = self.ldb.search(base=group_dn, + scope=SCOPE_BASE, + attrs=['member']) + except LdbError as err: + self.fail(err) + + orig_msg = group_res[0] + + # Add the user to the list of members. + members = list(orig_msg.get('member', ())) + self.assertNotIn(userdn, members, 'account already in Protected Users') + members.append(userdn) + + m = Message(group_dn) + m['member'] = MessageElement(members, + FLAG_MOD_REPLACE, + 'member') + self.ldb.modify(m) + + # Try entering the correct password 'x' times in a row, which should + # fail, but not lock the user out. + new_password = 'thatsAcomplPASS2' + for i in range(lockout_threshold): + with self.assertRaises( + NTSTATUSError, + msg='Invalid SAMR set_password accepted') as err: + print(f'Trying correct password, attempt #{i}') + net.set_password(newpassword=new_password, + account_name=username, + domain_name=creds.get_domain()) + + enum = ctypes.c_uint32(err.exception.args[0]).value + self.assertEqual(enum, ntstatus.NT_STATUS_ACCOUNT_RESTRICTION) + + res = self._check_account( + userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + lockoutTime=None, + userAccountControl=dsdb.UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0) + + # The user should not be locked out. + self.assertNotIn('lockoutTime', res[0]) + + # Ensure that the password can still be changed via LDAP. + self.ldb.modify_ldif(f''' +dn: {userdn} +changetype: modify +delete: userPassword +userPassword: {creds.get_password()} +add: userPassword +userPassword: {new_password} +''') + class PasswordTestsWithSleep(PasswordTests): def setUp(self):