summaryrefslogtreecommitdiff
path: root/source4/dsdb
diff options
context:
space:
mode:
authorJoseph Sutton <josephsutton@catalyst.net.nz>2022-02-09 13:50:10 +1300
committerStefan Metzmacher <metze@samba.org>2022-03-18 11:55:30 +0000
commitfd765aaa5b319ac997cb82b6fa296e6af2f67db9 (patch)
tree949816c464b153ff7a308a421382ca2801770a08 /source4/dsdb
parent3e0c94a345d82b25b43c0a93096b2a1779766094 (diff)
downloadsamba-fd765aaa5b319ac997cb82b6fa296e6af2f67db9.tar.gz
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 <josephsutton@catalyst.net.nz> Reviewed-by: Stefan Metzmacher <metze@samba.org>
Diffstat (limited to 'source4/dsdb')
-rwxr-xr-xsource4/dsdb/tests/python/password_lockout.py277
1 files changed, 277 insertions, 0 deletions
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'<SID={self.ldb.get_domain_sid()}-'
+ f'{security.DOMAIN_RID_PROTECTED_USERS}>')
+ 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'<SID={self.ldb.get_domain_sid()}-'
+ f'{security.DOMAIN_RID_PROTECTED_USERS}>')
+ 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'<SID={self.ldb.get_domain_sid()}-'
+ f'{security.DOMAIN_RID_PROTECTED_USERS}>')
+ 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):