367 lines
18 KiB
Diff
367 lines
18 KiB
Diff
From 4d0fd1a421ad4a3ca19ed954ee91fcc36413b017 Mon Sep 17 00:00:00 2001
|
|
From: Andrew Bartlett <abartlet@samba.org>
|
|
Date: Sun, 2 Sep 2018 18:03:06 +1200
|
|
Subject: [PATCH 11/17] CVE-2018-16857 selftest: Split up password_lockout into
|
|
tests with and without a call to sleep()
|
|
|
|
This means we can have a long observation window for many of the tests and
|
|
so make them much more reliable. Many of these cause frustrating flapping
|
|
failures in our CI systems.
|
|
|
|
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
|
|
Reviewed-by: Gary Lockyer <gary@catalyst.net.nz>
|
|
|
|
Autobuild-User(master): Andrew Bartlett <abartlet@samba.org>
|
|
Autobuild-Date(master): Mon Sep 3 06:14:55 CEST 2018 on sn-devel-144
|
|
|
|
(cherry picked from commit 74357bf347348d3a8b7483c58e5250e98f7e8810)
|
|
Backported as a dependency for:
|
|
BUG: https://bugzilla.samba.org/show_bug.cgi?id=13683
|
|
---
|
|
source4/dsdb/tests/python/password_lockout.py | 299 +++++++++---------
|
|
1 file changed, 157 insertions(+), 142 deletions(-)
|
|
|
|
diff --git a/source4/dsdb/tests/python/password_lockout.py b/source4/dsdb/tests/python/password_lockout.py
|
|
index d8710866f39..0022dee21ba 100755
|
|
--- a/source4/dsdb/tests/python/password_lockout.py
|
|
+++ b/source4/dsdb/tests/python/password_lockout.py
|
|
@@ -88,6 +88,42 @@ class PasswordTests(password_lockout_base.BasePasswordTestCase):
|
|
self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
|
|
lockOutObservationWindow=self.lockout_observation_window)
|
|
|
|
+
|
|
+ def use_pso_lockout_settings(self, creds):
|
|
+
|
|
+ # create a PSO with the lockout settings the test cases normally expect
|
|
+ #
|
|
+ # Some test cases sleep() for self.account_lockout_duration
|
|
+ pso = PasswordSettings("lockout-PSO", self.ldb, lockout_attempts=3,
|
|
+ lockout_duration=self.account_lockout_duration)
|
|
+ self.addCleanup(self.ldb.delete, pso.dn)
|
|
+
|
|
+ userdn = "cn=%s,cn=users,%s" % (creds.get_username(), self.base_dn)
|
|
+ pso.apply_to(userdn)
|
|
+
|
|
+ # update the global lockout settings to be wildly different to what
|
|
+ # the test cases normally expect
|
|
+ self.update_lockout_settings(threshold=10, duration=600,
|
|
+ observation_window=600)
|
|
+
|
|
+ def _reset_samr(self, res):
|
|
+
|
|
+ # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
|
|
+ samr_user = self._open_samr_user(res)
|
|
+ acb_info = self.samr.QueryUserInfo(samr_user, 16)
|
|
+ acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
|
|
+ self.samr.SetUserInfo(samr_user, 16, acb_info)
|
|
+ self.samr.Close(samr_user)
|
|
+
|
|
+
|
|
+class PasswordTestsWithoutSleep(PasswordTests):
|
|
+ def setUp(self):
|
|
+ # The tests in this class do not sleep, so we can have a
|
|
+ # longer window and not flap on slower hosts
|
|
+ self.account_lockout_duration = 30
|
|
+ self.lockout_observation_window = 30
|
|
+ super(PasswordTestsWithoutSleep, self).setUp()
|
|
+
|
|
def _reset_ldap_lockoutTime(self, res):
|
|
self.ldb.modify_ldif("""
|
|
dn: """ + str(res[0].dn) + """
|
|
@@ -615,22 +651,130 @@ userPassword: thatsAcomplPASS2XYZ
|
|
"samr",
|
|
initial_lastlogon_relation='greater')
|
|
|
|
- def use_pso_lockout_settings(self, creds):
|
|
+ def test_multiple_logon_krb5(self):
|
|
+ self._test_multiple_logon(self.lockout1krb5_creds)
|
|
|
|
- # create a PSO with the lockout settings the test cases normally expect
|
|
- #
|
|
- # Some test cases sleep() for self.account_lockout_duration
|
|
- pso = PasswordSettings("lockout-PSO", self.ldb, lockout_attempts=3,
|
|
- lockout_duration=self.account_lockout_duration)
|
|
- self.addCleanup(self.ldb.delete, pso.dn)
|
|
+ def test_multiple_logon_ntlm(self):
|
|
+ self._test_multiple_logon(self.lockout1ntlm_creds)
|
|
|
|
- userdn = "cn=%s,cn=users,%s" % (creds.get_username(), self.base_dn)
|
|
- pso.apply_to(userdn)
|
|
+ def _test_samr_password_change(self, creds, other_creds, lockout_threshold=3):
|
|
+ """Tests user lockout by using bad password in SAMR password_change"""
|
|
|
|
- # update the global lockout settings to be wildly different to what
|
|
- # the test cases normally expect
|
|
- self.update_lockout_settings(threshold=10, duration=600,
|
|
- observation_window=600)
|
|
+ # 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 = "cn=%s,cn=users,%s" % (username, 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 = "thatsAcomplPASS2"
|
|
+ net.change_password(newpassword=new_password.encode('utf-8'),
|
|
+ username=username,
|
|
+ oldpassword=creds.get_password())
|
|
+ creds.set_password(new_password)
|
|
+
|
|
+ # try entering 'x' many bad passwords in a row to lock the user out
|
|
+ new_password = "thatsAcomplPASS3"
|
|
+ for i in range(lockout_threshold):
|
|
+ badPwdCount = i + 1
|
|
+ try:
|
|
+ print("Trying bad password, attempt #%u" % badPwdCount)
|
|
+ net.change_password(newpassword=new_password.encode('utf-8'),
|
|
+ username=creds.get_username(),
|
|
+ oldpassword="bad-password")
|
|
+ self.fail("Invalid SAMR change_password accepted")
|
|
+ except NTSTATUSError as e:
|
|
+ enum = ctypes.c_uint32(e[0]).value
|
|
+ self.assertEquals(enum, ntstatus.NT_STATUS_WRONG_PASSWORD)
|
|
+
|
|
+ # check the status of the account is updated after each bad attempt
|
|
+ account_flags = 0
|
|
+ lockoutTime = None
|
|
+ if badPwdCount >= lockout_threshold:
|
|
+ account_flags = dsdb.UF_LOCKOUT
|
|
+ lockoutTime = ("greater", badPasswordTime)
|
|
+
|
|
+ res = self._check_account(userdn,
|
|
+ badPwdCount=badPwdCount,
|
|
+ badPasswordTime=("greater", badPasswordTime),
|
|
+ logonCount=logonCount,
|
|
+ lastLogon=lastLogon,
|
|
+ lastLogonTimestamp=lastLogonTimestamp,
|
|
+ lockoutTime=lockoutTime,
|
|
+ userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
+ msDSUserAccountControlComputed=account_flags)
|
|
+ badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
+
|
|
+ # the user is now locked out
|
|
+ lockoutTime = int(res[0]["lockoutTime"][0])
|
|
+
|
|
+ # check the user remains locked out regardless of whether they use a
|
|
+ # good or a bad password now
|
|
+ for password in (creds.get_password(), "bad-password"):
|
|
+ try:
|
|
+ print("Trying password %s" % password)
|
|
+ net.change_password(newpassword=new_password.encode('utf-8'),
|
|
+ username=creds.get_username(),
|
|
+ oldpassword=password)
|
|
+ self.fail("Invalid SAMR change_password accepted")
|
|
+ except NTSTATUSError as e:
|
|
+ enum = ctypes.c_uint32(e[0]).value
|
|
+ self.assertEquals(enum, ntstatus.NT_STATUS_ACCOUNT_LOCKED_OUT)
|
|
+
|
|
+ res = self._check_account(userdn,
|
|
+ badPwdCount=lockout_threshold,
|
|
+ badPasswordTime=badPasswordTime,
|
|
+ logonCount=logonCount,
|
|
+ lastLogon=lastLogon,
|
|
+ lastLogonTimestamp=lastLogonTimestamp,
|
|
+ lockoutTime=lockoutTime,
|
|
+ userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
+ msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
+
|
|
+ # reset the user account lockout
|
|
+ self._reset_samr(res)
|
|
+
|
|
+ # check bad password counts are reset
|
|
+ res = self._check_account(userdn,
|
|
+ badPwdCount=0,
|
|
+ badPasswordTime=badPasswordTime,
|
|
+ logonCount=logonCount,
|
|
+ lockoutTime=0,
|
|
+ lastLogon=lastLogon,
|
|
+ lastLogonTimestamp=lastLogonTimestamp,
|
|
+ userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
+ msDSUserAccountControlComputed=0)
|
|
+
|
|
+ # check we can change the user password successfully now
|
|
+ net.change_password(newpassword=new_password.encode('utf-8'),
|
|
+ username=username,
|
|
+ oldpassword=creds.get_password())
|
|
+ creds.set_password(new_password)
|
|
+
|
|
+ def test_samr_change_password(self):
|
|
+ self._test_samr_password_change(self.lockout1ntlm_creds,
|
|
+ other_creds=self.lockout2ntlm_creds)
|
|
+
|
|
+ # same as above, but use a PSO to enforce the lockout
|
|
+ def test_pso_samr_change_password(self):
|
|
+ self.use_pso_lockout_settings(self.lockout1ntlm_creds)
|
|
+ self._test_samr_password_change(self.lockout1ntlm_creds,
|
|
+ other_creds=self.lockout2ntlm_creds)
|
|
+
|
|
+
|
|
+class PasswordTestsWithSleep(PasswordTests):
|
|
+ def setUp(self):
|
|
+ super(PasswordTestsWithSleep, self).setUp()
|
|
|
|
def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
|
|
initial_logoncount_relation=None):
|
|
@@ -1065,12 +1209,6 @@ unicodePwd:: """ + base64.b64encode(new_utf16).decode('utf8') + """
|
|
self.use_pso_lockout_settings(self.lockout1ntlm_creds)
|
|
self._test_login_lockout(self.lockout1ntlm_creds)
|
|
|
|
- def test_multiple_logon_krb5(self):
|
|
- self._test_multiple_logon(self.lockout1krb5_creds)
|
|
-
|
|
- def test_multiple_logon_ntlm(self):
|
|
- self._test_multiple_logon(self.lockout1ntlm_creds)
|
|
-
|
|
def _testing_add_user(self, creds, lockOutObservationWindow=0):
|
|
username = creds.get_username()
|
|
userpass = creds.get_password()
|
|
@@ -1251,15 +1389,6 @@ userPassword: """ + userpass + """
|
|
msDSUserAccountControlComputed=0)
|
|
return ldb
|
|
|
|
- def _reset_samr(self, res):
|
|
-
|
|
- # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
|
|
- samr_user = self._open_samr_user(res)
|
|
- acb_info = self.samr.QueryUserInfo(samr_user, 16)
|
|
- acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
|
|
- self.samr.SetUserInfo(samr_user, 16, acb_info)
|
|
- self.samr.Close(samr_user)
|
|
-
|
|
def test_lockout_observation_window(self):
|
|
lockout3krb5_creds = self.insta_creds(self.template_creds,
|
|
username="lockout3krb5",
|
|
@@ -1286,120 +1415,6 @@ userPassword: """ + userpass + """
|
|
self._testing_add_user(lockout4ntlm_creds,
|
|
lockOutObservationWindow=self.lockout_observation_window)
|
|
|
|
- def _test_samr_password_change(self, creds, other_creds, lockout_threshold=3):
|
|
- """Tests user lockout by using bad password in SAMR password_change"""
|
|
-
|
|
- # 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 = "cn=%s,cn=users,%s" % (username, 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 = "thatsAcomplPASS2"
|
|
- net.change_password(newpassword=new_password.encode('utf-8'),
|
|
- username=username,
|
|
- oldpassword=creds.get_password())
|
|
- creds.set_password(new_password)
|
|
-
|
|
- # try entering 'x' many bad passwords in a row to lock the user out
|
|
- new_password = "thatsAcomplPASS3"
|
|
- for i in range(lockout_threshold):
|
|
- badPwdCount = i + 1
|
|
- try:
|
|
- print("Trying bad password, attempt #%u" % badPwdCount)
|
|
- net.change_password(newpassword=new_password.encode('utf-8'),
|
|
- username=creds.get_username(),
|
|
- oldpassword="bad-password")
|
|
- self.fail("Invalid SAMR change_password accepted")
|
|
- except NTSTATUSError as e:
|
|
- enum = ctypes.c_uint32(e[0]).value
|
|
- self.assertEquals(enum, ntstatus.NT_STATUS_WRONG_PASSWORD)
|
|
-
|
|
- # check the status of the account is updated after each bad attempt
|
|
- account_flags = 0
|
|
- lockoutTime = None
|
|
- if badPwdCount >= lockout_threshold:
|
|
- account_flags = dsdb.UF_LOCKOUT
|
|
- lockoutTime = ("greater", badPasswordTime)
|
|
-
|
|
- res = self._check_account(userdn,
|
|
- badPwdCount=badPwdCount,
|
|
- badPasswordTime=("greater", badPasswordTime),
|
|
- logonCount=logonCount,
|
|
- lastLogon=lastLogon,
|
|
- lastLogonTimestamp=lastLogonTimestamp,
|
|
- lockoutTime=lockoutTime,
|
|
- userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
- msDSUserAccountControlComputed=account_flags)
|
|
- badPasswordTime = int(res[0]["badPasswordTime"][0])
|
|
-
|
|
- # the user is now locked out
|
|
- lockoutTime = int(res[0]["lockoutTime"][0])
|
|
-
|
|
- # check the user remains locked out regardless of whether they use a
|
|
- # good or a bad password now
|
|
- for password in (creds.get_password(), "bad-password"):
|
|
- try:
|
|
- print("Trying password %s" % password)
|
|
- net.change_password(newpassword=new_password.encode('utf-8'),
|
|
- username=creds.get_username(),
|
|
- oldpassword=password)
|
|
- self.fail("Invalid SAMR change_password accepted")
|
|
- except NTSTATUSError as e:
|
|
- enum = ctypes.c_uint32(e[0]).value
|
|
- self.assertEquals(enum, ntstatus.NT_STATUS_ACCOUNT_LOCKED_OUT)
|
|
-
|
|
- res = self._check_account(userdn,
|
|
- badPwdCount=lockout_threshold,
|
|
- badPasswordTime=badPasswordTime,
|
|
- logonCount=logonCount,
|
|
- lastLogon=lastLogon,
|
|
- lastLogonTimestamp=lastLogonTimestamp,
|
|
- lockoutTime=lockoutTime,
|
|
- userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
- msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
|
|
-
|
|
- # reset the user account lockout
|
|
- self._reset_samr(res)
|
|
-
|
|
- # check bad password counts are reset
|
|
- res = self._check_account(userdn,
|
|
- badPwdCount=0,
|
|
- badPasswordTime=badPasswordTime,
|
|
- logonCount=logonCount,
|
|
- lockoutTime=0,
|
|
- lastLogon=lastLogon,
|
|
- lastLogonTimestamp=lastLogonTimestamp,
|
|
- userAccountControl=dsdb.UF_NORMAL_ACCOUNT,
|
|
- msDSUserAccountControlComputed=0)
|
|
-
|
|
- # check we can change the user password successfully now
|
|
- net.change_password(newpassword=new_password.encode('utf-8'),
|
|
- username=username,
|
|
- oldpassword=creds.get_password())
|
|
- creds.set_password(new_password)
|
|
-
|
|
- def test_samr_change_password(self):
|
|
- self._test_samr_password_change(self.lockout1ntlm_creds,
|
|
- other_creds=self.lockout2ntlm_creds)
|
|
-
|
|
- # same as above, but use a PSO to enforce the lockout
|
|
- def test_pso_samr_change_password(self):
|
|
- self.use_pso_lockout_settings(self.lockout1ntlm_creds)
|
|
- self._test_samr_password_change(self.lockout1ntlm_creds,
|
|
- other_creds=self.lockout2ntlm_creds)
|
|
-
|
|
|
|
host_url = "ldap://%s" % host
|
|
|
|
--
|
|
2.17.1
|