Flexible Security For Your Peace of Mind

Resetting the clock on Active Directory password expiration

(Last updated on September 24, 2020)

I recently worked with a customer who was implementing Specops Password Policy with Length-Based password aging. Usually we see customers use this to extend their maximum password age, for example: the current Active Directory maximum password age is 90 days; Specops length-based aging will be configured with the same ‘tier 1’ maximum password age, but users who chose to go above and beyond the minimum length requirement can keep their password much longer than that.

This customer, in contrast to most, already had a 180-day maximum password age in Active Directory but wanted to implement Specops in such a way where shorter passwords were still allowed; however if users choose to continue to use them they would now see their maximum password age reduced from 180 days to 90 days.

This is a fine strategy for guiding users to choose longer passwords without forcing them, but implemented presented a challenge: with Specops Password Policy, length-based password aging is not calculated until the first password change after the Specops policy is implemented. Since Active Directory stores a hashed password, and since hashes are always the same length regardless of the length of the password, we have no information to go on to reward users with longer passwords on day one), so when implementing a policy like the one above, all current passwords will have to expire after the tier 1 threshold of 90 days before length-based aging can take effect. The problem for this customer is a large percentage of their users already have password between 90 and 180 days old and those users would all see their passwords expired the day Specops Password Policy was implemented.

To ease the burden on the end users and helpdesk that might be caused by a sudden wave of password expirations, we came up with a way to extend the age of the users’ current password without modifying the policy configuration. We did this by exploiting an unusual but intended behavior in Active Directory.

The PwdLastSet Attribute and Requiring Password Change on Next Logon

Every user account has an attribute called pwdLastSet. This attribute is written by Active Directory with the current timestamp every time the user’s password is changed or reset.  We can inspect this attribute in the AD Users and Computers attribute tab or using the ActiveDirectory PowerShell module:

get-aduser username -properties pwdlastset,passwordlastset | fl samaccountname,pwdlastset,passwordlastset

Note: With PowerShell we must query the attribute passwordLastSet and not pwdLastSet to get a readable value – pwdLastSet returns the result is in a filetime format; you can convert this value to a readable DateTime but it is far easier to query passwordLastSet and let AD/PowerShell do the conversion for you.

Both Active Directory and Specops Password Policy calculate password expiration based on the pwdLastSet attribute. If the pwdLastSet timestamp + the maxPasswordAge in days is a date that falls in the past, the user’s password will expire and they will be forced to change it at next logon. An administrator cannot write a different timestamp to this attribute, nor can the administrator clear the value of this attribute directly. Rather, the administrator cannot clear the attribute by writing it directly.

Let’s see what happens when we check the box that says the user must change their password at next logon:

Now let’s inspect the pwdLastSet attribute value:

get-aduser username -properties pwdlastset,passwordlastset | fl samaccountname,pwdlastset,passwordlastset

There is actually no attribute on a user account that corresponds to that ‘user must change password at next logon’ checkbox. Instead, Active Directory sets the pwdLastSet value to 0, which makes it look as if the user’s password was never set. This simplifies the code elsewhere that computes if a user’s password has expired: if the user’s password was never set, then it was always set more than maxPasswordAge days ago and therefore must be changed at next logon!

Now what happens if we go back and uncheck that box?  What happens to the pwdLastSet attribute now?

get-aduser username -properties pwdlastset,passwordlastset | fl samaccountname,pwdlastset,passwordlastset

If checking ‘user must change password at next logon’ sets the pwdLastSet attribute to 0, when unchecking that box, AD has to write something else there. It cannot revert to the actual time the password was last set (since that information was wiped when the box was checked). So instead it writes the current time, just as it would on an actual password change. You can see here the exact date and time I tested this for writing this blog post.

We might agree this behavior seems a bit odd, as well as could potentially provide a loophole for admins to let password last longer than they should (which is true), but my customer had the brilliant idea to use this behavior to his advantage. By checking and un-checking ‘user must change password at next logon’ on every user, he could in effect reset the clock on password expiration as he rolled out Specops Password Policy. All users could keep their existing password for up to 90 days from the time this was done as per his new Specops password policy; any users who did not take advantage of the advance warning of this would be required to change their password 90 days later.

No one would suggest doing this manually for a large user base of course, so we wrote up some PowerShell scripts that will do this for us. 

First will populate a $users array with the users we want to target.  To do this based on an OU, we’ll set $targetOU to the distinguishedName of the desired OU:

$targetOU = "OU=Demo,OU=Users,OU=Specops,DC=specopsdemo1,DC=com"
$users = get-aduser -searchbase $targetOU -filter { passwordNeverExpires -eq $false -and pwdLastSet -gt 0 } -properties passwordLastSet

To instead target all members of a security group regardless of what OU they are in:

$targetGroup = "GroupName"
$users = Get-ADGroupMember -recursive $targetGroup | get-aduser -Properties passwordneverexpires,passwordlastset | Where-Object { $_.passwordNeverExpires -eq $false -and $_.passwordLastSet }

Note in the selection we exclude users where ‘Password Never Expires’ is set (you cannot set require change at next logon for these users) as well as users who are already required to change their password at next logon, as we imagine there might be a good reason for that.

After running the preferred selection query, you can just type $users and hit enter to confirm your selection.

To then set and un-set the require change password at next logon option, run the following code block:

foreach ($user in $users) {
    $outObject = new-object -typename psobject
    $outobject | Add-member -MemberType NoteProperty -Name distinguishedName -Value $user.distinguishedname
    $outobject | Add-Member -MemberType NoteProperty -Name OldPasswordLastSet -value $user.passwordlastset
    set-aduser $user -ChangePasswordAtLogon:$true
    set-aduser $user -ChangePasswordAtLogon:$false
    $outobject | Add-Member -MemberType NoteProperty -Name NewPasswordLastSet -value $(get-aduser $user -Properties passwordlastset).passwordlastset
    $outObject
} 

The script will set and un-set the option one user at a time and output the users as they are processed, including the old and new values for passwordLastSet.  If the old value is blank, that means the passwordLastSet attribute was already set to 0/never.

Here is an example of running the script against a target OU in the PowerShell ISE:

And instead against a target group:

>

Written by

Darren Siegel

Product Specialist, Specops Software

More Articles
Back to Blog