ADPWD-012: Passwords in HaveIBeenPwned Database
- Platform
- Active Directory
- Category
- AD Password & Lockout Policies
- Severity
- High
- Zero Trust pillar
- Identity (weight 2)
- Golden fixtures
- 3
- Branch coverage
- Observed: fixtures prove the verdicts they exercise
- Provenance
- baseline
What it checks
The check operates on the NT hash side of the AD database. It binds to a domain controller, requests the DRSUAPI GetNCChanges replication primitive (the same RPC used by DCSync) or reads a previously captured NTDS.dit + SYSTEM hive, and extracts the unicodePwd attribute (NT hash, RC4-MD4 of the UTF-16-LE password) for every user object where userAccountControl does not have ACCOUNTDISABLE (0x2) set. Each NT hash is then compared against the offline HaveIBeenPwned Pwned Passwords NTLM list (pwned-passwords-ntlm-ordered-by-hash-v8.txt or later), which is a sorted text file of MD4(UTF-16-LE(password)) hashes followed by an occurrence count. A binary search across the sorted file is O(log n) per lookup, so a 200,000-user domain is processed in seconds. The check explicitly does not transmit any hash off the host: the comparison is done entirely locally against a downloaded copy of the dataset. For each match, the check captures sAMAccountName, distinguishedName, memberOf, adminCount, pwdLastSet, lastLogonTimestamp, and the breach prevalence count from the HIBP dataset (so passwords seen 24 million times in breaches are escalated above passwords seen 3 times). DSInternals Test-PasswordQuality is the reference implementation; the check also accepts a pre-staged NTDS.dit copy via NTDSUtil IFM media so it can run on a Tier 0 jumpbox without touching the live DC.
Why it matters
A breached password is not a theoretical weakness; it is a credential that an attacker can paste into a tool and use immediately. Microsoft has reported on the Identity Security blog that more than 99.9 percent of identity compromises observed in Azure AD are blocked by simply rejecting known-breached passwords combined with MFA, which is why Azure AD Password Protection ships with the global banned password list. The blast radius is determined by which accounts hold the breached passwords. A breached password on a help desk technician account is a one-step path to password reset on user objects (ADPRIV abuse). A breached password on a service account with a SPN is one-step Kerberoast plus pass-the-password (ADKERB-001 + ADPWD-012 chained). A breached password on an account in Domain Admins, Enterprise Admins, or any Tier 0 group is forest compromise without any zero-day, without any 0-click, and without any lateral-movement chain. Credential stuffing is the number-one initial-access vector in the Verizon DBIR every year since 2018, and the wordlists used are exactly the HIBP corpus. The check matters because complexity policies, password length minimums, and even Azure AD Password Protection do not detect passwords that satisfied the policy when set but have since appeared in a breach.
Attack path
Step 1: Acquire the wordlist. The attacker downloads the HIBP Pwned Passwords NTLM ordered-by-hash file from haveibeenpwned.com/Passwords (free, no auth, distributed as a torrent and via Cloudflare). The list is 14 GB uncompressed and contains every NT hash that has ever appeared in a public breach, sorted for fast binary search. Step 2: Enumerate target accounts. The attacker queries the domain for valid usernames via LDAP, RPC SAMR, or by scraping the Global Address List from Exchange / Office 365. No credentials are required for unauthenticated SMB null sessions on legacy domains, or for OWA / ADFS username enumeration via timing differences. Step 3: Credential stuff or password spray. With the username list and the HIBP corpus, the attacker has two paths. Path A (online): Use kerbrute or DomainPasswordSpray to spray the top 50 HIBP passwords against every account (Spring2024!, Summer2024!, Password123!, Welcome1!), staying under the lockout threshold by spraying 1 password every N hours. Path B (offline): If the attacker has already obtained NT hashes via NTDS extraction, DCSync, or a memory dump, they sort their captured hashes and binary-search them against the HIBP file. Any match instantly reveals the plaintext-equivalent (the hash itself is the credential for pass-the-hash, but the breach prevalence count tells the attacker which accounts to prioritize for plaintext recovery via hashcat). Step 4: Authenticate. Hits from Path A produce direct interactive logons. Hits from Path B feed Mimikatz sekurlsa::pth /user:svc_account /domain:contoso.local /ntlm:hash for pass-the-hash, or Rubeus asktgt /user:svc_account /rc4:hash for overpass-the-hash to obtain a TGT and pivot via Kerberos. Step 5: Privilege escalation and persistence. Once authenticated as the breached-password account, the attacker enumerates BloodHound paths from this principal to Tier 0. If the account is in Domain Admins, the engagement is over in step 5. If not, the attacker continues to Kerberoast, ASREPRoast, ADCS abuse, or DCSync from whatever ACL paths are available. Persistence is established via a Skeleton Key, a DSRM password sync, or a new domain admin account before the original credential is detected and rotated.
How Guerrilla assesses it
Guerrilla executes Test-PasswordQuality (DSInternals) against a captured NTDS.dit + SYSTEM hive copy, or against a live replication snapshot pulled via Get-ADReplAccount with replication rights. The captured NT hashes are batch-compared against the offline HIBP Pwned Passwords NTLM list (latest version downloaded at audit time from haveibeenpwned.com/Passwords). Lookup uses a sorted-file binary search so 100k-user domains complete in under a minute. For each match the check captures sAMAccountName, distinguishedName, group memberships via tokenGroups expansion, adminCount, pwdLastSet, lastLogonTimestamp, userAccountControl flags, and the HIBP prevalence count (number of times the hash appears across known breaches). Results are bucketed into three severity tiers: Critical (account is in any Tier 0 group, has adminCount=1, or has a SPN), High (account is enabled, not stale, and not in a privileged group), Informational (account is enabled but has not logged on in 180+ days, indicating low risk of immediate stuffing success). The check never transmits hashes off the audit host: comparison is fully offline. As a runtime detection complement, Guerrilla recommends enabling Azure AD Password Protection on-premises agent (DC agent + proxy) which intercepts password changes at the LSA and rejects values that match Microsoft and custom banned lists before they ever land in NTDS.dit.
Recommended value
No active accounts using passwords found in the HaveIBeenPwned database
Remediation
Compare NT hashes against the HaveIBeenPwned Passwords database (downloadable hash list) using tools like DSInternals Test-PasswordQuality. Force immediate password changes on all accounts with matching hashes. Deploy Azure AD Password Protection or custom password filters to block known breached passwords going forward
Fixture-proven verdicts
Every verdict below is proven by a golden fixture in the module's gating test suite. This table derives from the last green run; it cannot be edited by hand.
| Scenario | Expected verdict |
|---|---|
| clean | PASS |
| known-bad | FAIL |
| no-data | Not Assessed |
Framework mappings
- NIST SP 800-53
- IA-5(1)
- CIS AD Benchmark
- 5.5.3
- MITRE ATT&CK
- T1110.002, T1078.002