Custom Password Filter Update (unable to log on after changing password with custom filter in place)

I had written and tested a custom Active Directory password filter – my test included verifying the password actually worked. The automated testing was to select a UID from a pool, select a test category (good password, re-used password, password from dictionary, password that doesn’t meet character requirements, password containing surname, password containing givenName), set the password on the user id. Record the result from the password set, then attempt to use that password and record the result from the bind attempt. Each test category has an expected result, and any operation where the password set or bind didn’t match the expected results were highlighted. I also included a high precision timer to record the time to complete the password set operation (wanted to verify we weren’t adversely impacting the user experience). Published results, documented the installation and configuration of my password filter, and was done.

Until the chap who was installing it in production rang me to say he couldn’t actually log in using the password he set on the account. Which was odd – I set one and then did an LDAP bind and verified the password. But he couldn’t use the same password to log into a workstation in the test domain. Huh?? I actually knew people who wanted *some* users to be able to log in anywhere and others to be restricted to LDAP-only logons (i.e. web portal stuff) and ended up using the userWorkstations attribute to allow logon to DCs only.

We opened a case with Microsoft and it turns out that their Password Filter Programming Considerations didn’t actually mean “Erase all memory used to store passwords by calling the SecureZeroMemory function before freeing memory.” What they meant was “If you have created copies of the password anywhere within your code, make sure you erase memory used to store those copies by calling SecureZeroMemory …”

Which makes SO much more sense … as the comments in the code I used as our base says, why wouldn’t MS handle wiping the memory? Does it not get cleaned well if you don’t have a custom password filter?? Remarked out the call to SecureZeroMemory and you could use the password on NTLM authentications as well as kerberos!

// MS documentation suggests doing this. I honestly don’t know why LSA
// doesn’t just do this for you after we return. But, I’ll do what the
// docs say…
// LJR – 2016-12-15 Per MS, they actually mean to wipe any COPIES you make
// SecureZeroMemory(Password->Buffer, Password->Length);

 

I’ve updated my version of the filter and opened an issue on the source GitHub project … but if anyone else is working a custom password filter, following MS’s published programming considerations, and finds themselves unable to use the password they set … see if you are zapping your copies of the password or the PUNICODE_STRING that comes in.

4 comments

  1. Avatar
    Leeren says:

    Again, I really appreciate the detailed reply. What is the importance of verifying the LDAP bind after checking the success of a password reset? Were there any instances where a successful password reset led to an unsuccessful bind, or vice versa? Also, were the perl scripts each run on your respective DCs? So they utilized external calls for things like ‘dsmod’? Thanks again!

    • Avatar
      Lisa says:

      You’re quite welcome. Verifying the LDAP bind was really paranoia. The password filter returns TRUE on failures (i.e. it accepts the password when something breaks). I wanted documentation to confirm that *under load* it didn’t do anything abjectly silly like start approving all passwords because the server got busy. Didn’t encounter any unexpected successes or failures, but I didn’t expect to either.

      We’ve got perl on the standard image, so I ran my test from a bunch of virtual desktops on servers across two data centres. Distributed the dsmod binary in conjunction with my script, and yeah the script made an external call and stuffed the output into a variable.

      my $strDSModResult = `dsmod user \”$strUID\” -pwd $strNewPassword -mustchpwd no -s $strDomainController -u $strAdminUID -p $strAdminPassword`;

      The initial test was targeted to a domain controller in a test domain. Some subsequent testing was done against a production domain controller that is in an isolated site (didn’t want real users changing their password against the server until testing had completed).

  2. Avatar
    Leeren says:

    Awesome post! I was wondering how exactly you timed the password reset operation? Was this included in the service itself which was logged. Or is there some way that Microsoft allows you to do this i.e. through event logs? Thanks!

    • Avatar
      Lisa says:

      Thanks 🙂 Quick answer is that I wrote a quick load script in perl that used the Time::HiRes module which measures microseconds. I use the same framework for most of our changes – the code to perform the test operation is different, but the timer and data analysis bits remain the same.

      I created test accounts with IDs that were algorithmically easy to walk — TestUser00001, TestUser00002, TestUser00003 … TestUser99999. In the script, I built an array of test types (banned words, contains surname, contains given name, contains user ID, does not meet general complexity requirements, and highly complex unique string that will work) and an associated array of expected results. Then used two pseudo-random number generators to get an ID (concatenate TestUser with pseudo-random number printf’d to zero pad) and a test type ($strTestTypes[$iPseudoRandomNumber]). Generated a password based on the test type (essentially a long if/elsif/else chain for each test type and formed a password with those parameters).

      Once I had a user whose password would be changed and a password to set, I got the start time ($t0 = [gettimeofday];). Then used dsmod to set the password and held the output in a variable. Grabbed the elapsed time ($fElapsedTime = tv_interval ($t0, [gettimeofday]);). Verified the result contained the string ‘succeeded’ where the password change operation was expected to succeed and not when the operation was expected to fail. Then used the new password in an LDAP bind and record that (error code 0 is success, error code 49 is invalid password) and verified it against the expected outcome.

      I wrote the ID, test type, elapsed time, and validation to a text file (I also wrote any that failed to validate, that is expected successes that failed or expected failures that succeeded, to the console – didn’t have any, but I didn’t want to trace through a million records to figure out if the filter wasn’t doing what we wanted). I ran it multi-threaded across multiple machines, so my output file was essentially ADPwdChange-$HOSTNAME-$iThread. Once I’d run a couple million password changes, I concatenated all of the output files into one master output.

      I then loaded the whole thing into Excel and ran a few statistical functions on the data set — max, min, average, and standard deviation. Knowing the max and min times, I divided that range into intervals (0-10ms, 11-25ms, 26-75ms, 76-150ms, 151-300ms, 301-500ms, 501-1000ms, >1000ms) and used countif on the timer column to determine how many results fell into each range. The intervals & # of password changes in that interval were graphed in a bar chart.

      I ran the script twice — once before the custom filter was installed so we had a baseline performance and once after the filter was running. For the general project team, I created a bar chart with both the “before filter” and “after filter” data so they could see there was not significant change to the user experience.

Leave a Reply

Your email address will not be published. Required fields are marked *