Skip to content

AD-ACC-005: Stale enabled user accounts

low   Accounts   MITRE ATT&CK T1078.002   Reads: LDAP

Enabled user accounts that have not logged on for 90 days or more: unwatched credentials that still work.

Also known as: stale-users

Why it matters

Accounts of people who left, test accounts and one-off service accounts tend to stay enabled. Their passwords are old and often weak, their owners will not notice a logon, and no one reads their alerts. Password spraying finds them, and a logon with a dormant account looks like any other user's.

How ad-doctor detects it

From the LDAP snapshot: every enabled user (computers and managed service accounts excluded, krbtgt and Guest skipped) whose last activity is more than 90 days before the scan time. Last activity is lastLogonTimestamp; when that is empty, pwdLastSet is used instead. Accounts with neither (never logged on and never given a password) are not reported.

lastLogonTimestamp is replicated, so one DC is enough, but it is only updated when the previous value is older than about 9 to 14 days. Treat the 90 days as approximate.

The finding says how many users matched; each object is a sAMAccountName, for example old_contractor.

Attributes and probes read
  • lastLogonTimestamp
  • pwdLastSet
  • userAccountControl
  • objectClass

Severity

Always low. A dormant account is not an attack path by itself; it is a credential nobody would miss and nobody watches. Dormant admins are also reported by AD-ACC-011 when their password is old.

False positives and limits

  • Cloud-only sign-ins. Users who only sign in to Microsoft 365 or other services through password hash sync never authenticate to a domain controller, so lastLogonTimestamp does not move.
  • Break-glass and disaster-recovery accounts are unused on purpose. Accept them in an accepted-risk file with an owner and an expiry date.

Fix

  1. List inactive users (read-only):

    Search-ADAccount -AccountInactive -UsersOnly -TimeSpan 90.00:00:00 |
        Where-Object Enabled |
        Select-Object SamAccountName, LastLogonDate, DistinguishedName
    
  2. Confirm with the owner (HR, the application owner) that each account is not needed.

  3. Disable the account and move it to a quarantine OU. This is what ad-doctor fix generates:

    Disable-ADAccount old_contractor -WhatIf
    
  4. Delete after a grace period, with the AD Recycle Bin enabled (AD-INF-002) so a mistake can be undone:

    Remove-ADUser old_contractor -WhatIf
    
  5. Automate it: run the query in step 1 on a schedule and feed it into the leaver process.

Verify

ad-doctor scan dc01.corp.example --ldaps -u 'CORP\auditor' --only AD-ACC-005

To accept the risk instead, add an entry to the file passed with --accept (see accepted risks):

[[accept]]
check = "AD-ACC-005"
objects = ["..."]        # optional; without it the whole check is accepted
reason = "..."
expires = 2027-03-31

References

Microsoft and vendors

MITRE ATT&CK

Available since 0.1.0.