🏢Active DirectoryAccountsPrivileged Access

AD logonCount per DC review: qualify totals before account retirement

Review AD logonCount by DC, preserve unknown readings, and verify current access and ownership before retiring an administrative account.

Younes AZABARBy Younes AZABAR9 min read
AD logonCount per DC review: qualify totals before account retirement

What an AD logonCount per DC Review Establishes

An AD logonCount per DC review asks where a directory counter came from before using it to retire an administrative account. A small value on one domain controller can open a review. It does not establish the account's domain-wide activity, its current permissions, or whether its owner still needs it.

The useful result is a review record: the account identity, every DC in scope, each returned value, unresolved reads, current access, and the owner's decision. Keep those observations together so an administrator can explain why an account was retained, investigated further, or entered an approved retirement process.

This guide proposes a read-only collection and decision workflow. The PowerShell examples adapt documented Microsoft Learn parameters for illustrative identities and servers; they were not executed against a customer domain. Replace the placeholders with verified values and use the ActiveDirectory module under an identity authorized to read the directory.

How the Counter Works

Microsoft's Logon-Count attribute reference describes a successful-logon counter maintained independently on each DC, without replication. It recommends querying every DC and summing their values. Zero means unknown. Retired DCs can leave missing history, and the documented ceiling of 65535 makes a saturated counter unsuitable for tracking activity on that DC.

Treat the attribute as a cumulative count with coverage and historical limits, rather than a timestamp. A count of 1–4 contains no date from which to calculate recent inactivity. Even a complete set of current reads cannot reconstruct contributions from retired controllers. Record these limitations beside any subtotal instead of describing it as the account's entire lifetime history.

Do not substitute badPwdCount when logonCount is unavailable. The adjacent badPwdCount triage workflow qualifies failure counters before an attack verdict. Its discipline of preserving server, time, and collection quality applies here, but a failure counter does not answer the successful-logon question.

Detection: Build an Account/DC Evidence Matrix

Step 1 — Fix the identity and scope

Start from the original finding and preserve its account identifier, collector endpoint, collection time, collector revision, and errors. Confirm the account's domain and the DC inventory with the directory operations team. The two hostnames below illustrate separate reads; they are not a complete DC list for an actual domain.

Choose a stable account identifier and verify it against the finding before repeating the read. Keep the DN or GUID alongside the displayed name in the review record. If the account cannot be resolved on a server, preserve the lookup error and investigate identity, scope, or directory availability. Do not create a numeric entry for an object that was not returned.

Microsoft documents identity selection, explicit server selection, and additional attributes in Get-ADUser. These adaptations request logonCount explicitly from a named DC rather than relying on default server selection:

Get-ADUser -Identity 'CN=ReviewAdmin,OU=Staff,DC=corp,DC=example' -Server 'dc01.corp.example' -Properties logonCount,adminCount -ErrorAction Stop
Get-ADUser -Identity 'CN=ReviewAdmin,OU=Staff,DC=corp,DC=example' -Server 'dc02.corp.example' -Properties logonCount,adminCount -ErrorAction Stop

Run the same documented read against each verified DC in scope, separately. Save the returned object and any error with the selected endpoint and read time. A successful connection alone does not establish that the requested attribute was returned. Inspect the property rather than allowing a later numeric conversion to turn an empty value into zero.

Step 2 — Preserve the raw observations

Use one row per account and DC. The matrix below is a recommended evidence structure, not sample output from a customer environment or fields automatically supplied by the detector.

FieldRecordReview purpose
AccountVerified DN or GUID and displayed nameAssociates every read with the same object
Domain and DCDomain context and explicit server endpointMakes the declared collection scope reviewable
Read timeStart and completion time in UTCExposes a collection spread across different moments
Requested attributeExplicit logonCount requestDistinguishes a deliberate read from default output
Raw valueReturned value without replacing empty dataPreserves the source observation
InterpretationKnown positive value, returned zero/unknown, absent, invalid, or failedPrevents collection gaps becoming activity conclusions
ErrorActual lookup, access, or transport error where presentGives the next reviewer a concrete recovery task

For a returned zero, retain the literal zero and mark the activity value unknown. For an absent attribute, retain the absence. For an invalid value, keep its original representation and classify the interpretation as unresolved. For a failed read, retain the error and leave the count unset. These states may require different follow-up even though none supplies a known activity count.

If a server is unavailable, keep its row in the declared scope. Assign the retry or investigation to an owner. A successful retry becomes a new observation with its own time; it should not erase the original collection failure. Likewise, preserve different observations if collection is repeated rather than choosing whichever value makes the review look complete.

Step 3 — Summarize without erasing uncertainty

Sum the known positive values while retaining the full matrix. Report the result as a known subtotal whenever any DC has an unknown value or was not successfully read. Alongside it, list which DCs contributed, which did not, the collection interval, and any saturated counter or missing historical contribution.

Do not silently drop unavailable servers from the scope or fill their cells with zero. A spreadsheet containing a numeric value in every row can still contain unresolved evidence. The summary should make that visible to the person deciding whether further collection is needed.

Use the same rule when comparing reviews. Keep account identity and DC coverage consistent, describe changes in inventory, and preserve the earlier matrix. A changed subtotal can reflect changed coverage; it does not automatically supply a recent-activity interval. If the decision requires a timeline, obtain separately qualified activity evidence for a stated period and document its coverage.

Remediation: Review Access and Ownership Before Retirement

Step 4 — Resolve the administrative marker

The detector uses adminCount=1 to select candidates. Before calling the account an administrator, establish its present access. The stale adminCount cleanup workflow explains why the marker can survive past privileged membership. Follow its membership and permission review rather than assuming the flag is a current-rights inventory.

For the retirement decision, request a review of direct and nested groups, the primary group, relevant delegated permissions, and the resources the account can administer. Keep the reviewer, scope, and unresolved paths in the case. An ordinary-looking direct group list does not close the broader access question.

This can produce distinct outcomes: an account with justified current access; a former privileged account with residual state; unjustified permissions that require a targeted change; or access that remains unverified. Record the actual evidence for the classification. Do not clear adminCount simply to remove this finding, and do not treat flag cleanup as account retirement.

Step 5 — Obtain the owner's decision

Identify the accountable owner and ask which workflows depend on the account. Review the stated purpose against available operational evidence and dependencies. Include scheduled, infrequent, recovery, and emergency uses where applicable to that account; their relevance must come from the owner and the environment, not from an assumed explanation for a small counter.

Review outcomeDecision to recordNext action
Justified access and identified ownerRetain under a documented purposeRecord the access scope and next review
Access exceeds the approved roleReview a targeted privilege changeObtain approval and validate affected workflows
Owner supports retirement and dependencies are resolvedEnter the approved retirement processPreserve evidence, recovery arrangements, and change ownership
Unknown DC reads, owner, or dependenciesKeep the case unresolvedAssign the missing evidence and follow-up

The proposed process separates the counter review from the authorization to change access. If retirement is approved, use the organization's account lifecycle procedure for the verified identity. Choose the change and recovery method from that approved plan. This guide does not prescribe a bulk disable or deletion based on the detector's threshold.

Step 6 — Validate the recorded outcome

After an approved change, reread the same account on the documented servers and preserve the new results separately. The Get-ADUser identity, server, and property parameters documented above also support this repeatable read:

Get-ADUser -Identity 'CN=ReviewAdmin,OU=Staff,DC=corp,DC=example' -Server 'dc01.corp.example' -Properties logonCount,adminCount -ErrorAction Stop

Repeat the access review relevant to the change and have the owner confirm affected workflows. Record the actual account state, permissions, remaining dependencies, errors, and follow-up. A disappearance from the finding alone does not establish that permissions or business dependencies were handled correctly.

How EtcSec Detects This

The reviewed etc-collector check is ADMIN_LOGON_COUNT_LOW, in Accounts, with Info severity. At collector revision afca1b9f7bba6f076d6a6493371486071001452b, admin-logon-count-low.go:36 selects enabled users with AdminCount=true, excludes LogonCount=0, and retains a nonzero count below 5. It applies no inactivity period. The threshold is a product predicate, not a rule for retiring accounts.

Count is the number of matching users, not a sum of logons. The detector returns the finding even when that count is zero; affected-user details are attached when requested and matches exist. A match is an investigation lead from the collected view.

The LDAP provider limits that view. client.go:460 retrieves users through the selected connection without aggregating DC counters. parser.go:41 requests logonCount; the user parser reads it through getIntAttr, whose absent-value and invalid-integer handling can produce zero. The detector therefore cannot distinguish all collection states that the independent matrix preserves. Excluding zero prevents an unknown count from becoming a positive finding, but does not verify domain-wide use.

The check computes no domain total and establishes no effective rights, ownership, dependencies, or recent-activity timeline. adminCount is its selection marker, not proof of present privilege. Use ADMIN_LOGON_COUNT_LOW to open the review, then qualify each DC observation and the account's access before deciding its future.

Explore the identity security pages that support this topic