ADPWD-017: LAPS Password Expiration

Platform
Active Directory
Category
AD Password & Lockout Policies
Severity
Medium
Zero Trust pillar
Identity (weight 1)
Golden fixtures
3
Branch coverage
Observed: fixtures prove the verdicts they exercise
Provenance
baseline

What it checks

The check enumerates every (objectClass=computer) under the defaultNamingContext and reads the expiration attribute in priority order: - msLAPS-PasswordExpirationTime (Windows LAPS, schema introduced by KB5025227 in April 2023) - ms-Mcs-AdmPwdExpirationTime (legacy Microsoft LAPS, schema extension shipped with the original MSI) Both attributes are stored as a Windows FILETIME (Int64, 100-nanosecond intervals since 1601-01-01 UTC). Guerrilla converts the raw value with [DateTime]::FromFileTimeUtc() and compares the result against the current UTC time. A computer is reported in one of four states: - Expired: the timestamp is in the past. The CSE failed to rotate at the scheduled interval. - Missing: neither attribute is populated. LAPS has never written to this object, either because the CSE is not installed, the GPO does not target it, or the machine account lacks SELF write permission on the attribute. - Stale Beyond Policy: the timestamp is in the future but exceeds today + the policy ceiling (default 30 days). Indicates a per-object override or a misconfigured GPO PasswordAgeDays setting. - Healthy: timestamp is in the future and within the policy ceiling. The check also reads the PasswordAgeDays value the GPO is publishing (HKLM\Software\Microsoft\Windows\CurrentVersion\LAPS\Config\PasswordAgeDays for Windows LAPS, HKLM\Software\Policies\Microsoft Services\AdmPwd\PasswordAgeDays for legacy LAPS) on a sample of endpoints so the operator can see whether the AD-side baseline matches the GPO-side baseline. Computer objects that are disabled or whose lastLogonTimestamp is older than the configured stale window are excluded from the FAIL count and listed separately so a long-offline laptop fleet does not create false positives.

Why it matters

LAPS exists to defeat lateral movement by Pass-the-Hash (T1550.002) and Pass-the-Ticket (T1550.003). Before LAPS, the local Administrator account on every workstation in an OU image shared the same NT hash, so a single LSASS dump from any endpoint yielded SMB and WMI admin to every other machine in the same image, a primitive the SDL community calls Same-Local-Admin-Password. LAPS breaks that primitive by giving each computer a unique random password that rotates on a fixed schedule. The control depends entirely on rotation actually happening. An expired expiration timestamp means the password on disk has been static for longer than policy allows, and in the worst case it means a credential that was exposed in a prior incident (a stolen LAPS backup, a help-desk audit log, a forensic image, a screenshot in a ticket) is still valid today. The blast radius scales with how many machines are stale at once: a single expired endpoint is a hygiene issue, a stale population of hundreds is a deployment failure that re-creates the pre-LAPS lateral movement surface. The check also catches a quieter persistence pattern: an attacker with WriteProperty on ms-Mcs-AdmPwdExpirationTime can push the timestamp far into the future on machines they care about, suppressing rotation while keeping a known plaintext password indefinitely (a variant of T1098, Account Manipulation).

Attack path

1. Foothold and discovery: the attacker, holding any authenticated session, queries LDAP for computer objects and asks for ms-Mcs-AdmPwdExpirationTime and msLAPS-PasswordExpirationTime. Get-ADComputer -Filter * -Properties ms-Mcs-AdmPwdExpirationTime, msLAPS-PasswordExpirationTime returns the entire fleet without raising any non-standard event because both attributes are world-readable by default in legacy LAPS deployments (only the password attribute is ACL-protected). 2. Identify stale population: the attacker filters for objects where the expiration timestamp is in the past or unset. Tools: LAPSToolkit (Jake Liefer), Get-LAPSComputers from PowerView, or a one-line LDAP filter (&(objectClass=computer)(ms-Mcs-AdmPwdExpirationTime<=132000000000000000)). 3. Read protected attribute on healthy LAPS targets: for endpoints where LAPS did rotate, the attacker tests whether they hold ExtendedRight Read-LAPS-Password (ms-Mcs-AdmPwd or msLAPS-Password / msLAPS-EncryptedPassword) via misconfigured delegations. ADACL checks for LAPS read rights pair directly with this check. 4. Reuse static credentials: on machines flagged Expired or Missing, the local Administrator password may still be the image default or an older LAPS value. The attacker tries known prior LAPS dumps from incident-response artifacts, help-desk tickets, or stolen ELK indices. 5. Persistence via expiration manipulation: with GenericWrite on a target computer object, the attacker sets ms-Mcs-AdmPwdExpirationTime to a far-future FILETIME (Set-ADComputer -Replace @{"ms-Mcs-AdmPwdExpirationTime" = "133000000000000000"}). The CSE on that endpoint will not rotate again until the timestamp passes, freezing the password the attacker has captured. 6. Lateral movement: with a working local-admin credential, the attacker pivots via SMB (impacket-psexec, smbexec), WMI (wmiexec), or WinRM. Because the credential is local, no domain logon event fires on a DC, and Defender for Identity does not see it.

How Guerrilla assesses it

Guerrilla binds to the defaultNamingContext with the credentials of the invoking principal and issues a paged LDAP search for (objectClass=computer), requesting msLAPS-PasswordExpirationTime, ms-Mcs-AdmPwdExpirationTime, lastLogonTimestamp, userAccountControl, operatingSystem, and distinguishedName. For each object the check picks the newer Windows LAPS attribute when present and falls back to legacy LAPS. The Int64 FILETIME is converted via [DateTime]::FromFileTimeUtc() and compared against [DateTime]::UtcNow. Computers are bucketed as Expired (now > timestamp), Missing (attribute not present and not null), Stale Beyond Policy (timestamp > now + PasswordAgeDays ceiling), or Healthy. Disabled accounts (userAccountControl bit 0x2) and accounts whose lastLogonTimestamp is older than the configured stale-computer threshold (default 90 days, aligned with ADSTALE-001) are tagged Excluded and shown in a separate table. The check also performs a sampled GPO read: it resolves the LAPS GPOs by querying gpLink on the OUs that contain the failing machines, pulls the Registry.pol values for PasswordAgeDays, and reports any mismatch between the GPO-published ceiling and the observed AD timestamps. Output rows include computer DN, OS, last logon, expiration timestamp, days-since-expiration (negative for healthy), the source attribute (legacy vs Windows LAPS), and the OU-level GPO ceiling.

Recommended value

LAPS password expiration set to 30 days. No computers with expired LAPS passwords

Remediation

Review LAPS password expiration dates on computer objects using Get-ADComputer -Filter * -Properties ms-Mcs-AdmPwdExpirationTime. Identify computers with expired passwords and investigate the LAPS CSE functionality on those machines. Configure GPO to set password age to 30 days maximum

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.

Verdict scenarios for ADPWD-017
ScenarioExpected verdict
cleanPASS
known-badWARN
no-dataNot Assessed

Framework mappings

NIST SP 800-53
IA-5(1)
ANSSI
R42
CIS AD Benchmark
5.7.2
MITRE ATT&CK
T1078.003