Systems Admin

Secure Active Directory Passwords from Breaches

Introduction

Security is essential for every organization, and the front door to most environments is still the Active Directory password. When a single account password is breached, attackers can pivot from that one foothold into mailboxes, file shares, line-of-business applications, and eventually the rest of the domain. The cost is rarely just the data that leaks — it is also the days or weeks of incident response, forensics, customer communication, and regulatory paperwork that follow.

Strong security starts with two unglamorous fundamentals: auditing the passwords your users have today, and preventing them from picking known-bad passwords tomorrow. In this article you will learn how to do both for Active Directory using Lithnet Password Protection and the Have I Been Pwned (HIBP) compromised passwords list — the same dataset of nearly a billion real-world breached password hashes that powers HIBP’s public lookup.

The Two Configurations You Need

Hardening AD passwords against breaches is two distinct projects, and you should plan to do both:

  1. Audit account passwords. Compare every user’s current password hash in AD against the HIBP compromised list. The output is a CSV of accounts whose passwords have already appeared in a public breach.
  2. Secure account passwords. Configure a Group Policy that intercepts password set/change operations and rejects any candidate password whose hash is in the HIBP store — so users and admins simply cannot pick a known-bad password going forward.

The audit alone is useful but insufficient: a user with a pwned password can be told to change it, and then re-enter another pwned password the next time. The block-on-set GPO closes that loop and stops the next bad password from getting into AD in the first place.

Both halves of the solution use the same tool: Lithnet Password Protection for Active Directory — a free, open-source domain controller agent that maintains a local copy of the HIBP hash store and exposes both an auditing cmdlet and a password filter that the LSA calls during every password change.

Set Up Lithnet Password Protection for Active Directory

Step 1 — Install Lithnet Password Protection on the DC

Sign in to a Domain Controller as a Domain Admin. Lithnet PP installs onto every DC where you want password filtering enforced; for an audit-only deployment, installing on a single DC is enough.

Download the latest LithnetPasswordProtection-x.x.xx.msi installer from the project’s GitHub releases page. The release notes list any breaking changes between versions; in general, taking the latest stable build is the right call.

Lithnet Password Protection for Active Directory v1.1.53 release page on GitHub showing the change log and the Assets section at the bottom of the page with a red arrow drawn pointing at the LithnetPasswordProtection-1.1.53.msi installer download link
Download the latest Lithnet Password Protection for Active Directory installer (.msi) from the GitHub releases page

Start the installer and walk through the wizard. There are no configuration choices to make — click Next through the welcome and license screens, then Install on the Ready to Install step. The wizard registers the password filter DLL with the Local Security Authority and creates the empty hash store directory under C:\Program Files\Lithnet\Active Directory Password Protection\Store\v3.

Step 2 — Sync the HIBP Hash Store

The installer ships with an empty hash store. To populate it, open an elevated PowerShell window on the DC and run the synchronization cmdlet:

Sync-HashesFromHibp

The cmdlet streams the entire HIBP compromised passwords list (currently around 1,048,576 pages of SHA-1 hash prefixes) down from the public HIBP API and writes them into the local store. Expect this to take roughly an hour on a fast connection — the volume of data is significant.

Two Windows screenshots: the Lithnet Password Protection for Active Directory installer Setup Wizard at the Ready to Install step with Back, Install, and Cancel buttons (Install highlighted by a red arrow), and a Windows PowerShell administrator window running the Sync-HashesFromHibp cmdlet pulling the Have I Been Pwned compromised password hashes into the local store
Run through the Lithnet PP installer on the Domain Controller, then start an elevated PowerShell session and run Sync-HashesFromHibp to pull the HIBP hash store

When the sync completes, PowerShell prints a summary object with the counters. A first sync against an empty store looks something like this:

OperationStart           : 1/23/2024 7:13:35 PM
OperationFinish          : 1/23/2024 8:30:35 PM
Duration                 : 01:17:00.2187755
PagesRetrieved           : 1048576
PagesWithChanges         : 1048576
PagesUnchanged           : 0
NewHashesImported        : 931856448
ExistingHashesDiscarded  : 0
TotalHashesProcessed     : 931856448

That is roughly 932 million compromised password hashes loaded into the local store on this DC.

Step 3 — Verify the Hash Store

Open File Explorer and browse to:

C:\Program Files\Lithnet\Active Directory Password Protection\Store\v3\p

You should see a directory full of small Data Base Files with hex-string names (0000, 00A, 00B, …). Right-click the parent v3 folder, choose Properties, and confirm the on-disk size is roughly 12.2 GB. That number is a useful sanity check that the sync actually completed and the store is populated.

Two stacked screenshots: a PowerShell results object listing OperationStart, OperationFinish, Duration 01:17:00, PagesRetrieved 1,048,576, NewHashesImported 931,856,448 and TotalHashesProcessed 931,856,448 from the Sync-HashesFromHibp run, followed by a File Explorer window showing the Lithnet store directory C:\Program Files\Lithnet\Active Directory Password Protection\Store\v3\p filled with hex-named Data Base Files
After the sync completes, PowerShell shows roughly 932 million hashes imported and the local store directory at C:\Program Files\Lithnet\Active Directory Password Protection\Store\v3\p fills with the database files

Schedule Sync-HashesFromHibp to run weekly via Task Scheduler so the local store stays current as new breaches are added to HIBP. Subsequent syncs are far smaller (only changed pages are pulled), typically minutes rather than an hour.

Audit Existing AD Passwords

Step 4 — Run the Audit Script

Lithnet PP ships with a sample Audit-Passwords.ps1 script in its module directory. Copy it into C:\scripts on the Domain Controller (or wherever you keep maintenance scripts) and run it from an elevated PowerShell session:

C:\scripts\.\Audit-Passwords.ps1

The script enumerates every user object in the domain, reads the NT hash, computes the SHA-1 hash of the underlying password (Lithnet has secure access to the unicode-NT-to-SHA1 derivation), and looks up each hash in the local Lithnet store. Wait for it to complete — on a domain with thousands of users this can take several minutes.

Two screenshots: a Windows folder Properties dialog reporting a folder size of 12.2 GB confirming the Lithnet HIBP store on disk, and a File Explorer view of C:\scripts containing two files - get-pwned-users.csv (CSV file) and Audit-Passwords.ps1 (Windows PowerShell Script) with a red arrow pointing at the CSV output file
The Lithnet store consumes about 12.2 GB on disk; place the Audit-Passwords.ps1 script in C:\scripts on the DC and it produces get-pwned-users.csv after running

Step 5 — Review the get-pwned-users.csv Output

When the script finishes, it produces C:\scripts\get-pwned-users.csv — a CSV with one row per Active Directory account that has a pwned password. Typical columns include accountName, UPN, pwdLastSet, lastLogon, and accountDisabled.

Open the CSV in Excel and you will see something like this:

Microsoft Excel showing the get-pwned-users.csv output file with columns accountName, UPN, pwdLastSet, lastLogon, accountDisabled, listing several Active Directory user accounts (Joshua Hunter, Peter Campbell, Carol Grant, Richard Hunter, Paul Allen, Ruth Dickens, Zoe Roberts, Dominic Marshall, Megan Hart, Fiona Marshall, John Maverick, Mohammed Frotak) with TRUE/FALSE flags indicating whether each accountDisabled
The audit CSV lists every Active Directory account whose current password hash matches one in the HIBP compromised passwords list

Now you have a concrete remediation list. The honest next step is uncomfortable but necessary: contact each affected user, tell them their current password matches a known breach, and require them to change it. Do not include the breached password in the email — pointing the user at the public HIBP search lets them verify it themselves without you ever transmitting the password.

But there is a deeper problem here: nothing prevents the user from changing their password to another pwned password. Without a real-time block, you would be running this audit weekly and chasing the same users in a loop. That is the job of the next section.

Prevent Users From Setting Pwned Passwords

The block-on-set behavior is what turns Lithnet PP from an audit tool into a true control. With the GPO below in place, every password set/change request flows through the password filter, the candidate password is hashed, the hash is checked against the local store, and the operation is rejected if the hash matches a known breach — before the new password is ever written to AD.

Step 6 — Create the GPO

On the Domain Controller, open Group Policy Management. Right-click the Domain Controllers OU and choose Create a GPO in this domain, and Link it here. In the New GPO dialog, name the GPO LithnetPP and click OK.

Two screenshots: Group Policy Management Console with the Domain Controllers OU right-clicked and the context menu showing Create a GPO in this domain, and link it here highlighted in red, and below it the New GPO dialog with the Name field set to LithnetPP and the OK button highlighted
In Group Policy Management, right-click the Domain Controllers OU, choose Create a GPO in this domain, and link it here, name it LithnetPP, and click OK

Linking on the Domain Controllers OU is intentional: the password filter runs inside lsass on the DC that processes the password change. Linking the policy at the DC OU level guarantees every DC enforces the same setting.

Step 7 — Edit the GPO and Find the Setting

Right-click the LithnetPP GPO and choose Edit. In Group Policy Management Editor, navigate to:

Computer Configuration
  > Policies
    > Administrative Templates
      > Lithnet
        > Password Protection for Active Directory
          > Default Policy

Find and double-click the setting Reject passwords found in the compromised password store.

Two screenshots: GPMC showing a right-click on the LithnetPP GPO with Edit highlighted in the context menu, and the Group Policy Management Editor open at Computer Configuration / Policies / Administrative Templates / Lithnet / Password Protection for Active Directory / Default Policy with the setting Reject passwords found in the compromised password store highlighted in the right-hand pane
Right-click LithnetPP and choose Edit, then drill into Computer Configuration > Administrative Templates > Lithnet > Password Protection for AD > Default Policy and double-click Reject passwords found in the compromised password store

Step 8 — Enable the Reject Policy

In the policy dialog:

  1. Select the Enabled radio button at the top.
  2. In the Options pane, tick both checkboxes:
    • Enable for password set operations — admin-initiated resets via ADUC, Reset-ADAccountPassword, etc.
    • Enable for password change operations — user-initiated changes via Ctrl+Alt+Del or the self-service portal.
  3. Click OK.
Reject passwords found in the compromised password store GPO setting dialog with the Enabled radio button selected at the top, and below it in the Options pane two checkboxes both ticked: Enable for password set operations and Enable for password change operations, with the OK button at the bottom highlighted by a red arrow
Set the policy to Enabled and tick both Enable for password set operations and Enable for password change operations, then click OK

Note: The Lithnet ADMX has many more settings — banned-words lists, complexity beyond Microsoft’s built-in rules, length thresholds, accept-on-length policies, and so on. You can layer them on, but be aware that every additional rule makes legitimate password creation harder for users. Start with the HIBP reject and add complexity gradually based on real friction reports.

Important: Reboot the Domain Controller after enabling the policy. The Lithnet password filter is loaded by lsass at boot; the new GPO settings only take effect on the next start.

Test the New Policy

Verify the policy works in three different scenarios:

  1. Active Directory Users and Computers (admin reset): create a new account or reset an existing one. Try a password from the HIBP list, then a unique one.
  2. Active Directory Users and Computers (admin reset on existing user): reset an existing user’s password with both a breached and a clean password.
  3. Domain-joined Windows device (user change): sign in as a regular user, press Ctrl+Alt+Del > Change a password, and try both kinds.

The walkthrough below covers scenario 1 because the others produce identical filter behavior.

Reset a Password to a Pwned Value

In Active Directory Users and Computers, right-click a user account (in this example, Ricky Springer) and choose Reset Password. In the dialog, enter a password that you know is in the HIBP list. If you are unsure whether a candidate is breached, paste it into the public HIBP Pwned Passwords search first — HIBP uses the same SHA-1 prefix lookup that Lithnet uses locally and never transmits the full hash.

Two screenshots: Active Directory Users and Computers with a user account (Ricky Springer) right-clicked and the Reset Password option highlighted in red in the context menu, and below it the Have I Been Pwned Pwned Passwords web page showing a queried password as 'Oh no - pwned! This password has been seen 22,528 times before'
Test the policy by right-clicking a user in ADUC and choosing Reset Password; you can verify a candidate password against the public HIBP Pwned Passwords search first

For this example we use Password01 — a notoriously common test password that has appeared in breaches tens of thousands of times.

Type the password in both fields and click OK. AD rejects the change with the standard error:

Windows cannot complete the password change for Ricky Springer because: The password does not meet the password policy requirements. Check the minimum password length, password complexity, and password history requirements.

Two screenshots: the Reset Password dialog in ADUC with New password and Confirm password fields filled in and the OK button highlighted, followed by an Active Directory Domain Services error popup reading 'Windows cannot complete the password change for Ricky Springer because: The password does not meet the password policy requirements. Check the minimum password length, password complexity and password history requirements.' which is what Lithnet returns for a pwned password
When a pwned password (such as Password01) is entered, ADUC shows a generic password-policy error – Lithnet PP rejected the change because the SHA-1 of the password is in the HIBP store

The error message is generic by design — AD does not surface the “rejected because pwned” reason to the calling client, just the same wording it would use for any password-policy violation. Lithnet PP does log the specific reject reason to the DC’s event log under the Lithnet provider, which is where to look when a user complains they cannot reset their password.

Reset to a Clean Password

Repeat the same steps but choose a password that has never been in a breach — long, unique, generated by a password manager. The reset succeeds. Lithnet’s SHA-1 lookup against the local store returned no match, and the password filter let the operation through to the LSA as normal.

Operational Notes

  • Sync schedule. Run Sync-HashesFromHibp weekly via Task Scheduler. New hashes are added to HIBP whenever new breaches are processed, and a stale store is a partially-blind store.
  • Disk space. The store is roughly 12 GB at the time of writing and grows slowly. Make sure your DC system drive can absorb this on every DC where you install Lithnet.
  • Replication is not needed. Each DC has its own local hash store; there is no replication concern. Just make sure every DC runs the sync.
  • Pair with MFA. Even with this control in place, treat the password as one factor of two. Multi-factor authentication on every privileged account, every external-facing service, and every VPN endpoint stays the most important compensating control if a password ever does leak.
  • Log monitoring. Forward the Lithnet event log to your SIEM. Repeated rejects from the same account over a short window can indicate password-spray attempts hitting your filter, which is useful threat-intel.

Conclusion

You learned how to audit and harden Active Directory against breached passwords using Lithnet Password Protection and the HIBP compromised-passwords dataset. The setup is a one-time effort — install the agent, sync the store, drop the GPO, and reboot the DCs — and the result is a domain where simply using a known-bad password is no longer possible.

Always pair this control with regular auditing (so existing pwned passwords get rotated out) and with multi-factor authentication (so a leaked password is not enough on its own to compromise an account). With those three layers in place, the account-password attack surface in your environment shrinks dramatically.

Leave a Reply