ADPRIV-018: Privileged Accounts Never Logged In
- Plataforma
- Active Directory
- Categoría
- AD Privileged Account Security
- Severidad
- Medium
- Pilar de Zero Trust
- Identity (peso 1)
- Fixtures de referencia
- 3
- Cobertura de ramas
- Observada: los fixtures prueban los veredictos que ejercitan
- Procedencia
- baseline
Qué comprueba
The check pulls the full set of accounts carrying adminCount=1 (the marker that SDProp has stamped a protected ACE on the object) and joins it against the resolved transitive membership of the AdminSDHolder-protected groups: Domain Admins, Enterprise Admins, Schema Admins, Administrators, Account Operators, Backup Operators, Server Operators, Print Operators, Replicator, Cert Publishers, Domain Controllers, Read-only Domain Controllers, Key Admins, and Enterprise Key Admins. For each protected account it then reads the logon-history attributes: - lastLogonTimestamp (replicated, accurate to within 9 to 14 days) - lastLogon (per-DC, queried from every reachable DC and reduced to the maximum) - lastLogonDate (the cooked PowerShell projection of lastLogonTimestamp) - logonCount (per-DC, summed across reachable DCs) - pwdLastSet (to distinguish a never-logged-on account that has had its password set from a freshly provisioned one) - whenCreated (to compute the account age) - userAccountControl (to surface ACCOUNTDISABLE, PASSWORD_NOTREQD, DONT_EXPIRE_PASSWORD, and SMARTCARD_REQUIRED) An account is flagged when lastLogonTimestamp is null, every per-DC lastLogon value is zero or 1601-01-01, and the per-DC logonCount sums to zero. The age (whenCreated to now) is reported so that a freshly created service account on day one is not treated the same as a five-year-old orphan.
Por qué importa
A privileged account that no owner is monitoring is a permanent open door. Three concrete failure modes recur in incident response engagements. First, provisioning artifact: a service team requested an admin account during a migration, the project ended, the password was set to a vendor default or a shared value from a runbook, and the account was never claimed. The password rarely rotates because no one notices the maxPwdAge warning. Second, attacker persistence: a threat actor who briefly held DCSync or Account Operator rights creates a new user, adds it to a privileged group, and never logs in with it. The account sits dormant for months as a re-entry vector and survives credential rotation events because defenders focus on accounts with recent activity. Third, test or break-glass drift: a tiger-team account from a penetration test, a vendor support account, or a forgotten break-glass that nobody documented sits in Domain Admins with PASSWORD_NOTREQD or DONT_EXPIRE_PASSWORD set. Because the account has no logon history it produces no telemetry, so SIEM correlation rules built on logon volume, geography, or host affinity have nothing to alert on. The blast radius matches the privileged group: a never-used account in Domain Admins is functionally identical to a live Domain Admin from the attackers point of view, and is easier to abuse because there is no legitimate owner whose password rotation, MFA prompt, or sign-in notification might tip off defenders.
Ruta de ataque
1. Discovery: from any authenticated context the attacker enumerates protected accounts with one LDAP filter, (&(objectCategory=user)(adminCount=1)), and joins the result against logon history. Get-ADUser -Filter "AdminCount -eq 1" -Properties LastLogonTimestamp, pwdLastSet, whenCreated, userAccountControl returns the candidate set in a single round trip. Tools that automate this include PowerView (Get-DomainUser -AdminCount), BloodHound (the AdminCount user property is collected by SharpHound by default), and ldapsearch with the same filter. 2. Selection: the attacker ranks candidates by age and password state. An account created years ago, with pwdLastSet equal to whenCreated and lastLogonTimestamp null, is almost certainly a forgotten provisioning artifact whose password matches a vendor default, a runbook value, or one of the well-known weak strings (Welcome1, P@ssw0rd, the company name). 3. Password recovery: where the password is unknown the attacker first checks for PASSWORD_NOTREQD (a blank password is permitted) and then for DONT_REQUIRE_PREAUTH (AS-REP roastable). An AS-REP roast against a never-logged-on account is silent (no failed logon, no DC event 4625) because the request is a normal KRB_AS_REQ. The recovered TGT-encrypted blob is cracked offline with hashcat mode 18200. 4. Credential reset, where DCSync or Account Operator rights are already held: the attacker resets the dormant accounts password to a known value. Because no human is watching the account, the reset event (4724) does not generate a help-desk ticket and is not correlated to a phishing or business-process anomaly. 5. Use: the attacker logs on for the first time, often from outside business hours, from a workstation that no real admin uses, and immediately runs DCSync, Kerberoast, or GPO-modify against the domain. Because the lastLogonTimestamp updates only every 9 to 14 days, the initial logon may not even replicate before the attacker has completed the objective. 6. Persistence: the attacker leaves the account in place. Even if the immediate access vector is closed, the dormant privileged account remains as a permanent re-entry path.
Cómo lo evalúa Guerrilla
Guerrilla queries the directory with (&(objectCategory=user)(adminCount=1)) to retrieve the candidate set, then resolves transitive membership of every AdminSDHolder-protected group via the SID-based well-known group lookups (Domain Admins S-1-5-21-<domain>-512, Enterprise Admins S-1-5-21-<root domain>-519, Schema Admins -518, Administrators S-1-5-32-544, Account Operators -548, Backup Operators -551, Server Operators -549, Print Operators -550, Cert Publishers, Key Admins, Enterprise Key Admins). For every account the check reads lastLogonTimestamp from the bound DC and, where reachable, queries lastLogon from every other DC in the site to defeat the per-DC nature of that attribute. logonCount is summed across the same DC set. The check distinguishes between accounts that have never logged on (lastLogonTimestamp null AND every lastLogon zero AND logonCount sum zero) and accounts that simply have not logged on recently (lastLogonTimestamp present but older than the stale threshold, which is covered separately by ADSTALE-003 and ADPRIV-017). Account age is computed from whenCreated, and findings older than 90 days are escalated. The check explicitly excludes the built-in krbtgt account (which never interactively logs on by design) and tombstoned objects. Output includes the distinguishedName, the privileged group path that confers the protected ACE, the account age in days, the userAccountControl decoded flags, and the pwdLastSet age so analysts can triage without re-querying AD.
Valor recomendado
No privileged accounts with null LastLogonTimestamp. All privileged accounts actively used by their assigned owners
Remediación
Identify privileged accounts that have never logged in using Get-ADUser -Filter {AdminCount -eq 1} -Properties LastLogonTimestamp | Where-Object {$_.LastLogonTimestamp -eq $null}. Investigate each account to determine if it is needed. Disable or remove unnecessary accounts from privileged groups
Veredictos probados con fixtures
Cada veredicto de esta tabla está probado por un fixture de referencia en la suite de pruebas que valida el módulo. La tabla se deriva de la última ejecución en verde; no puede editarse a mano.
| Escenario | Veredicto esperado |
|---|---|
| clean | PASS |
| known-bad | WARN |
| throttled | Not Assessed |
Mapeos a marcos de referencia
- NIST SP 800-53
- AC-2(3), AC-2
- CIS AD Benchmark
- 4.6.1
- MITRE ATT&CK
- T1078.002