NOTE: This article only relates to organizations with an on-premise Active Directory that sync passwords to Azure Active Directory.
Have you ever come across an account where there was an indicator of compromise (IOC) and the account’s password was changed, but the compromise still continued? If not, you may have just been getting lucky in some of your incident response procedures.
In an environment where you utilize both an on-premise Active Directory with an Active Directory Federation Services server and sync to Azure Active Directory (AAD) utilizing Azure Active Directory Connect, you have the option to utilize another technology called Password Hash Sync. This is actually a very in-depth technology that hashes the password multiple times and syncs it with AAD. The reasoning for this technology is so that not only is more of your Active Directory environment backed up, but also so federated services can be accessed directly through AAD without coming back through your on-premise ADFS environment.
In the image below, the “Federation Trust” arrow represents the brokerage utilizing AAD Connect and Password Hash Sync. Once your sign-in is authorized by ADFS,you can then access federated applications through Azure. This means you no longer need to come back to ADFS except for re-authentication or if your token is invalidated.
When you have a successful authentication or authorization to an application federated with Azure, you receive two artifacts. The two artifacts are known as an access token and a refresh token. An access token is utilized to access a protected resource. A refresh token is used to obtain new access or refresh the current token pair when the access token expires.
You can think of authentication like renting an apartment. When you sign a lease, that is your authentication with the property manager. In return, they give you a key. That key works specifically to your apartment lock and you can keep using that key to get in without talking to your landlord again. The key in this metaphor is the two above tokens. The lock on your apartment door is the protected resource.
Access tokens continue until they expire and there is currently no way today to revoke an access token within Azure.
Access tokens continue until they expire and there is currently no way today to revoke an access token within Azure. The default access token lifetime is one hour, however, the lifetime is currently configurable. The minimum allowable is 10 minutes. The maximum allowable is 24 hours. This means that no matter what you do in your environment, if a threat actor obtains an access token and knows how to play that access token, they can get in until that one hour ends, regardless of anything you do regarding the user’s account. There is a special trick with migrating mailboxes on-premise and off-premise if you have on-premise Exchange and use O365, but that is not guaranteed to still work or continually work as of this article.
Refresh tokens…default expiration is…until revoked.
Refresh tokens continue until expiration but can be revoked. The default expiration is – wait for it – “until revoked.” Meaning a refresh token can be used indefinitely. However, inactive times do play a factor. If a token is not used at all for a certain period, then the refresh token expires. The default is 90 days. Configurable down to 10 minutes and up to 90 days. So if a refresh token is used every 89 days (when on the default setting), it will work forever until it is revoked. There are a few ways that refresh tokens are or can be revoked. When any password is changed and Azure is aware of the password change, all refresh tokens are revoked. Azure pays attention to a attribute field called “last password change timestamp” to maintain awareness of these changes. Disabling accounts also revokes all refresh tokens. In addition to those scenarios, a PowerShell command can be ran to revoke refresh tokens.
Using the AzureAD PowerShell module,you can run the following command to revoke all refresh tokens on a specific user:
Get-AzureADUser -SearchString firstname.lastname@example.org | Revoke-AzureADUserAllRefreshToken
More Microsoft documentation on token lifetimes.
The synchronization between on-premise Active Directory and Azure Active Directory with Password Hash Sync are where the faults may still lie. An informed threat actor can use this to their advantage in continually using a refresh token even after a password has been changed for a user
If you are changing passwords utilizing your on-premise Active Directory, checking the box that says “user must change password at next logon” actually causes Password Hash Sync to not synchronize the new password to AAD by default. The logic from Microsoft on this is that the new password is no longer secret. The person changing the password and the user both know the new password. Azure Active Directory takes a stance on only trusting passwords of which are actually secret.
You can see this when looking at AAD Connect event logs for the following event:
The following password changes are filtered from synchronization. DN=CN=username,OU=XX,OU=XX,DC=domain,DC=com, PasswordSyncResultType=FilteredByTarget <forest-info> <forest-name>DOMAIN.COM</forest-name> <connector-id>1234567890</connector-id> </forest-info>
In the above event log, you will see a field called ‘PasswordSyncResultType’ with a result of ‘FilteredbyTarget’. In Microsoft’s documentation for troubleshooting password hash sync, you can find the result of ‘FilteredbyTarget’ to mean “Password is set to User must change password at next logon. Password has not been synchronized.”
This means that if your incident response for a compromised account involves changing the password with on-premise Active Directory to a temporary password and checking that box so that a user will then change the password again, AAD is not yet aware of your password change until the password is changed the second time by the user.
So, if a threat actor has obtained the refresh token, they can continually play that refresh token and access federated resources, such as Exchange Online, even after you have “changed the user’s password,” until the password is changed again or refresh tokens are revoked. An example scenario where this gap in time may be large is if a user is on vacation or it’s 10:00pm and they will change it tomorrow morning at 9:00am when they get into the office.
Refined Incident Response or Configuration Change
There are several options that can protect against what is outlined in this article. All of these options involve process changes except one that involves a change in the way password hash sync works. It is important to do what you believe is best for your organization while also keeping in mind that process failures happen more often than configuration failures.
- No longer check the box that states “user must change password at next logon” when changing a password and rely on the user to change the password again.
- Change the password without the box that states “user must change password at next logon” and then change it again with the box selected.
- After changing a compromised accounts credentials, run the mentioned PowerShell cmdlet to revoke all refresh tokens for the account.
- Change the password in Azure Active Directory instead of on-premise Active Directory. Note that this will only work if you have write-back enabled so it can write back to your on-premise Active Directory.
- Implement the following change (that Microsoft has thankfully added functionality for) against your AAD environment. This will force these “non-secret” or “temporary passwords” to sync with AAD. The field “ForcePasswordResetOnLogonFeature” is set to “$false” by default and can be changed. Run the following command via PowerShell.
"Set-ADSyncAADCompanyFeature -ConnectorName "<AAD Connector name>" -ForcePasswordResetOnLogonFeature $true"
Credit to Daniel Stefaniak, Microsoft Program Manager of Azure AD PG for his knowledge transfer in this space as well as reviewing this article for me.
- Credit to @neumarke for reminding me that changing the password directly in AAD is also a remediation step to mitigate this issue. I have added it as potential solution #4.
Comments are closed.