ADPWD-022: Lockout Observation Window
- Platform
- Active Directory
- Category
- AD Password & Lockout Policies
- Severity
- Medium
- Zero Trust pillar
- Identity (weight 1)
- Golden fixtures
- 2
- Branch coverage
- Observed: fixtures prove the verdicts they exercise
- Provenance
- baseline
What it checks
Guerrilla performs an LDAP bind to the writable domain naming context and reads the lockOutObservationWindow attribute on the domain root object (objectClass=domainDNS). The value is stored as a negative Windows FILETIME in 100-nanosecond units; the check converts it to absolute minutes using TimeSpan.FromTicks(Math.Abs(value)).TotalMinutes. The check then enumerates every Password Settings Object (PSO) under CN=Password Settings Container,CN=System,<domainDN> and reads msDS-LockoutObservationWindow, msDS-LockoutThreshold, msDS-LockoutDuration, and msDS-PSOAppliesTo on each. For every scope (domain default plus each PSO), the check evaluates three conditions: - lockOutObservationWindow >= 15 minutes - lockOutObservationWindow >= lockoutDuration (the spray-out-the-counter rule) - lockoutThreshold > 0 (a non-zero threshold is a prerequisite for the window to mean anything) If any of the three fails, the scope is reported. The check correlates the result with ADPWD-006 (Account Lockout Policy) so a 0-threshold finding suppresses the observation-window finding for the same scope (the window is moot if no lockout is configured).
Why it matters
Password spray is the dominant credential-access primitive against Active Directory in 2024 and 2025 (CISA AA23-263A, Microsoft Midnight Blizzard incident reports). The attacker tries one or two passwords per account across the entire user population, then waits, then tries again. The observation window defines how long they have to wait. If the threshold is 5 and the window is 5 minutes, an attacker can try 4 passwords every 5 minutes per account forever without locking anyone. If the window is 30 minutes, the same attacker can try 4 passwords every 30 minutes, which on a 10,000-account population still yields hundreds of thousands of attempts per day but is orders of magnitude slower than a short-window forest. A second failure mode is window-shorter-than-duration. If lockoutDuration is 30 minutes and the observation window is 10 minutes, an account that locks at attempt N resets its counter to 0 at minute 10 even though it is still locked out until minute 30. When the lockout expires at minute 30, the counter is already empty, so the attacker has a fresh threshold immediately. The intended behavior, per the Microsoft Account Lockout Policy specification, is that the observation window equals or exceeds the lockout duration so the counter cannot reset while the account is locked. A third failure mode is policy inversion via a permissive PSO. A Fine-Grained Password Policy applied to a service-account group can declare a 1-minute observation window even when the domain default is 30 minutes, and because PSOs override the domain policy for their applies-to scope, the result is that the most attractive targets (service accounts with predictable naming and elevated rights) become the easiest to spray. The check enumerates every PSO so that an attacker-favorable PSO does not hide behind a compliant domain default.
Attack path
1. Reconnaissance: the attacker performs an authenticated LDAP query against the domain root and reads lockoutThreshold, lockOutObservationWindow, and lockoutDuration. A Domain Users principal is sufficient. With PowerView: Get-DomainPolicyData -Policy Domain | Select LockoutBadCount, ResetLockoutCount, LockoutDuration Or natively: Get-ADDefaultDomainPasswordPolicy 2. PSO enumeration: the attacker reads every PSO so they can pick the most permissive scope: Get-ADFineGrainedPasswordPolicy -Filter * | Select Name, LockoutThreshold, LockoutObservationWindow, LockoutDuration, AppliesTo 3. Target enumeration: the attacker harvests sAMAccountName values via LDAP (anonymous on legacy forests, authenticated with any account on modern forests), kerbrute userenum, or BloodHound. They prioritize accounts inside an attacker-favorable PSO scope. 4. Spray pacing: with Spray365, MSOLSpray, kerbrute passwordspray, or DomainPasswordSpray (Beau Bullock), the attacker calculates safe-attempt-per-window as (threshold - 1) and sleeps (observation_window + buffer) between rounds. A 5/5 policy (threshold 5, window 5 minutes) allows 4 attempts per account per 5 minutes indefinitely. Invoke-DomainPasswordSpray -Password 'Summer2026!' -Delay 5 -Domain corp.contoso.com 5. Credential discovery: on a 10,000-account forest with a 4-per-5-minutes pace, the attacker can test five seasonal passwords (Summer2026!, Autumn2026!, Winter2026!, Spring2026!, Welcome1!) across the entire population in under an hour without locking a single account. 6. Detection evasion: because no account ever locks, Event ID 4740 (account locked) is never written. The attacker is visible only in 4625 (bad password) events, which are typically high-volume and unfiltered in mid-maturity SOCs. 7. Pivot: each valid credential is tested against VPN, OWA, ADFS, and Entra ID single sign-on. Hybrid identity makes any AD password equally usable against the Microsoft 365 tenant via Seamless SSO or Password Hash Sync.
How Guerrilla assesses it
Guerrilla uses System.DirectoryServices.Protocols to bind to the domain root (RootDSE -> defaultNamingContext) and read the four lockout-related attributes in a single search: lockoutThreshold, lockOutObservationWindow, lockoutDuration, msDS-Behavior-Version. The 100-nanosecond LargeInteger values are normalized to minutes. The check then issues a second paged search under CN=Password Settings Container,CN=System,<domainDN> for objectClass=msDS-PasswordSettings and pulls msDS-LockoutThreshold, msDS-LockoutObservationWindow, msDS-LockoutDuration, msDS-PasswordSettingsPrecedence, and msDS-PSOAppliesTo on each PSO. The applies-to list is resolved to group names. The check is read-only, runs against any reachable DC, and requires only Domain Users membership. The finding is downgraded to Informational when lockoutThreshold is 0 (a 0-threshold disables lockout entirely, which is a separate ADPWD-006 finding), and upgraded when the observation window is below the lockout duration (the counter-reset-during-lockout race).
Recommended value
Observation window of 15-30 minutes, matching or exceeding the lockout duration
Remediation
Configure in Default Domain Policy: Computer Configuration > Policies > Windows Settings > Security Settings > Account Policies > Account Lockout Policy > 'Reset account lockout counter after' = 15-30 minutes. Ensure this value is equal to or greater than the lockout duration to prevent attackers from waiting out the counter between spray attempts
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 |
|---|---|
| known-bad | FAIL |
| throttled | Not Assessed |
Framework mappings
- NIST SP 800-53
- AC-7
- CIS Benchmark
- 1.2.3
- ANSSI
- R35
- CIS AD Benchmark
- 5.3.3
- MITRE ATT&CK
- T1110.001, T1110.003