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
lastLogonTimestamppwdLastSetuserAccountControlobjectClass
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
lastLogonTimestampdoes 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¶
-
List inactive users (read-only):
-
Confirm with the owner (HR, the application owner) that each account is not needed.
-
Disable the account and move it to a quarantine OU. This is what
ad-doctor fixgenerates: -
Delete after a grace period, with the AD Recycle Bin enabled (AD-INF-002) so a mistake can be undone:
-
Automate it: run the query in step 1 on a schedule and feed it into the leaver process.
Verify¶
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
Related checks¶
References¶
Microsoft and vendors
- Regularly check for and remove inactive user accounts in Active Directory
- The LastLogonTimeStamp attribute: what it was designed for and how it works
- Search-ADAccount
- ANSSI Active Directory checklist (vuln_user_accounts_dormant)
MITRE ATT&CK
Available since 0.1.0.