Author: Michael Varley, DFIR Analyst
Most people will have had the annoying experience of trying to think of a new password that is both complex enough to be secure and memorable enough to be useful. We have all been there, standard complexity requirements are 8 characters long, 1 number and 1 special character.
When being forced to do this every 30, 60 or 90 days, it is easy for people to get fatigued and start working around the complexity requirements. This isn’t malicious on the part of the end user, but an example of security getting in the way of people who just want to do their jobs.
During our Digital Forensics and Incident Response (DFIR) engagements, it is exceedingly common to see Threat Actors dumping credential stores containing usernames and password hashes.
In one recent engagement, we discovered that the dumped LSASS file was still present on an anonymous file hosting service. We were able to acquire a copy and, when we investigated further, we found no password complexity was in place and a surprising number of end users had weak passwords, such as “welcome”, “letmein”, “january2023” or “CompanyName”.
Weak User Passwords
There’s no default option available in Active Directory to prevent these types of weak passwords, and when password complexity is enforced, end users will typically just add an exclamation mark or 123 to the end of the password to defeat them.
Default Active Directory Password Complexity Settings
This got our team to thinking – surely there must be a way to blocklist certain passwords and/or password hashes?
Enter the OpenPasswordFilter (OPF).
https://github.com/jephthai/OpenPasswordFilter
OpenPasswordFilter is an open-source custom password filter DLL and user space service to prevent the adoption of weak Active Directory passwords by users.
There are commercial options available, but often they are in the “call for pricing” range of cost. There are two significant advantages of adopting the OPF solution:
Sounds too good to be true, right? Well, we tested it in a lab environment and the results were very promising. Let’s step through it.
Our lab environment consists of a Windows 10 endpoint, connected to a 2016 Windows Server that is configured to act as a primary Domain Controller and host Active Directory. It was a bit empty to begin with, so we populated AD using BadBlood (Cue: Taylor Swift).
Executing BadBlood via PowerShell
Approximately 2,500 Objects Later
Now that we have a populated Active Directory, I’ve added a user account we can use for our testing. A long time ago, in a galaxy far, far away…
A Test Account Successfully Added
OpenPasswordFilter does come with an automatic installer, but I’m a glutton for punishment and to be honest, when it comes to things like this, I really like to know the ins and outs and how things work.
OpenPasswordFilter works by introducing a DLL that hooks into the LSASS process. When an end user requests a password change, LSASS compares against the configured complexity requirements in Active Directory.
If the requested password passes AD complexity checks, it asks OpenPasswordFilter. OpenPasswordFilter hashes a pre-configured list of prohibited passwords and compares the requested password against them. It then returns a Pass/Fail to LSASS, which then handles the rest of the password reset process as usual.
OPF comes with just two objects – OpenPasswordFilter.dll and OPFService.exe.
OpenPasswordFilter Objects
Installation and configuration is a relatively simple task. First, we add OpenPasswordFilter.dll to %windir%\Windows\System32.
OpenPasswordFilter.dll in System32
We’ll be using OpenPasswordFilter.exe to create an auto-start service, so I’ve dropped it in its own folder on the C:\ drive. It can be anywhere you’d like, really.
OpenPasswordFilter.exe on the C:\ Drive
Using the command prompt and the scheduled tasks commandlet, we create a new scheduled task to execute the OpenPasswordFilter executable each time the Domain Controller restarts.
Creating the OPF Auto-Start Service
I’ve then restarted the Domain Controller to check the task was actually added, and that OpenPasswordFilter did, indeed, start as expected – I’ve been stung before.
Sure enough, this time, the service started successfully after a restart of the Domain Controller.
Confirming the OpenPasswordFilter Service Started Successfully
Now that we have created the Scheduled Task, it’s time to build our password lists. We have two to create, one that is an exact match and one that is a partial match (i.e. anything that contains the provided string(s)).
There are two you can download from the OpenPasswordFilter GitHub and these would be a great place to start, but for the purposes of testing and writing this blog, I’ve created my own.
These password lists go in the same directory as the OpenPasswordFilter executable we placed on the C:\ drive earlier.
Two Password Lists, One for Exact Matches and One for Partial Matching
As mentioned above, one of the big benefits of OpenPasswordFilter is that new passwords can be added to the exact match or partial match lists without requiring a restart of Domain Controllers, which can be a challenging ask within an enterprise environment.
A simple solution to keeping these lists updated across multiple Domain Controllers is to use a version control solution such as GitHub to maintain a single source list, then have them synchronise on a daily or weekly basis.
For the next step, we’ve logged into our Windows 10 endpoint as LUKE_SKYWALKER to better replicate the end user experience before testing our exact and partial match password lists.
May The Force Be With You
We send a CTRL + ALT + DEL signal and then click “Change Password”.
For the exact match test, I tried the password “W31c0m3123!” and for the partial match, I tried the password “1l0v3triskele!” where the partial match is on “triskele”.
The Exact Match Password
The Partial Match Password
As expected, the password change attempt fails, and the end user is presented with a message that the password failed complexity requirements.
Password Change Failure
Whilst the test was indeed a success, the downside is that the error message presented to the end user isn’t descriptive as to why the password request failed – It doesn’t mention that this password is prohibited, or contains a prohibited string.
If an organisation was to implement a solution such as this, communications would need to be provided to end users ahead of such an implementation to make them aware of what is an is not prohibited. Additionally, onboarding documentation for new colleagues joining the business would also need to be updated to reflect these changes, and advise what passwords are, and are not, accepted.
Using a password filter to ban specific passwords, or passwords that contain specific strings, will likely increase the time it takes for a threat actor to crack password hashes. Whilst not impossible, the increase in time taken may deter adversaries that are looking for a “quick win”.
Combine a password filter along with multi-factor authentication will significantly reduce the likelihood of an adversary achieving initial access into an environment using compromised credentials, reducing the likelihood of your organisation suffering a cyber-attack by this methodology.
If you’d be interested in learning more about this topic, don't hesitate to get in touch.
Subscribe_