Prevent fraud and increase security of your on-prem network by filtering Active Directory credentials against global databases of compromised passwords.
If your corporate environment uses cloud based domain, such as Azure AD, you are probably using MFA for end-user authentication. (If you’re not, you absolutely should. Go and enable it now.) However, if you’re a government entity, Fortune 500 mega corporation or any other organization with an on-premise domain, chances are you rely solely on passwords for user authentication.
One of the most popular attack vectors (social techniques aside) is using a dictionary of leaked credentials against an AD-joined computer. Unfortunately, AD does not provide an out of the box solution. Yes, there are password policies (complexity, age, etc.), but they have no way of checking if a newly proposed password has been leaked before.
Cue the LSA
Luckily, Microsoft’s LSA provides a Password Filter functionality, which basically takes a proposed password and runs it through a series of DLLs (defined in HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Notification Packages). This was used by authors of OpenPasswordFilter (OPF) to create a simple open source solution which checks password candidates against a provided (in a form of .txt files) dictionary.
In order to make this solution more flexible, I forked OPF into another repository, called opf-web, available on GitHub:
My goal was to decouple the decision-making process from OPF service and turn it into a simple web service, which can be deployed internally. A sample implementation is available here:
There are two main reasons for this approach:
- I need Internet access to dynamically check password candidates against global leaks (https://haveibeenpwned.com/) and Active Directory controllers should not have Internet access.
- A web service allows for a separate development environment not tied to AD controller or Windows DLLs. Meaning you can easily modify your business logic without having to touch the AD. And you can use any programming language that you want.
A complete flow of my solution is shown below:
Timeline of events is as follows:
- An end-user submits a password change request using any AD joined endpoint.
- This request is forwarded to some AD Controller (more on that below).
- The controller’s LSA checks Windows registry for a list of DLLs to run the new password candidate against.
- Some of those are built into Windows, and provide the usual password policy enforcement.
- Then, we finally hit our opf DLL.
- The first function which is called is PasswordFilter(). It receives the username and proposed password as its arguments.
- Inside this C++ function, we perform a local socket call to an opf service daemon, passing the arguments.
- We’re now in a somewhat easier to code C# environment. We can now connect to AD’s database and extract some additional information about the user (in my case: their cellphone number and email address). At this point, we hash the password candidate, since we’re about to leave the secure enclave of an AD controller.
- The proposed password’s hash along with user data extracted from AD is now sent via HTTP to our opf web app. This should be deployed on a separate machine, as it will require access to external resources (which AD controllers shouldn’t have).
- Our web app can now run wild with the hash and try to validate it against the leak databases. In my sample implementation I show a quick Redis lookup (as of today you need 64 GB of RAM to host haveibeenpwnd’s complete database in Redis). Or you can hit the Internet and use haveibeenpwnd’s API directly. Or implement any other policy applicable to your company.
- Finally, our web app makes a decision and returns it via HTTP response.
- It is received by the opf service…
- …which it turns returns it to the calling PasswordFilter() function.
- At this point, PasswordFilter() informs the LSA if the new password is accepted or not.
- (Optionally) if the password was accepted, a separate process calls another function in our DLL: PasswordChangeNotify(). I use a similar call stack to have another web app URL pinged when the password has been successfully changed. More on it below.
- The decision is ultimately returned to our end user, who receive a regular Windows message telling them if their password was changed or not.
When a password is successfully updated, LSA calls another DLL function, PasswordChangeNotify(). This allows us to propagate this information into our web app, and act accordingly.
First, we can send the user an email & a text message with the usual disclaimer “if it wasn’t you, something bad is going on”.
Second, considering we’re in an on-premise AD environment, there might be other legacy programs deployed in your institution. Or perhaps you’ve got a third party vendor who has yet to embrace the SSO (or charges a 500% premium for that feature). In all those cases, you can now create a new password for your end-user or at least force a change next time they login.
(Obviously, the web app never received the new password proposal in plain text, so a different / random password has to be used in all those cases)
Day to Day
There are two little headaches related to changing AD passwords.
First, the LSA DLLs have to be deployed to every single AD controller (unless it’s a read-only slave). You never know which one will be hit with the password change request, so you need to cover all of them. No biggie.
Second, because Active Directory hasn’t been updated since 1974, there is no way to return a supplemental message to end-user if we reject their password proposal. They will always get the beloved “your password doesn’t meet the complexity requirements”.
This is where our web app comes in handy. As long as it was responsible for the denial (and not some other LSA DLL), we know exactly WHY we didn’t like the new password. We can therefore email/sms our end user with a more user-friendly explanation.
Ultimately, I had no issues with LSA filters deployed in production. This is by no means a replacement for MFA in on-prem networks, but it does deal with one of the most popular attack vectors.
Most of the credits go to OpenPasswordFilter folks, who released their DLL code under GNU rather than trying to turn it into a cash cow – like literally every other LSA based solution out there.