AD account deletion: turning event 4726 into a useful alert
Windows logs event 4726 on a domain controller every time a user account is deleted. An alert on every 4726 is easy to build and gets ignored within a week, because help desks delete accounts all day. The event is still valuable. It just needs a question attached to it.
Prerequisite: "Audit User Account Management" must be enabled in the Advanced Audit Policy on your domain controllers, and their Security logs must reach your SIEM.
The base query
Start by making the event readable: who deleted which account, and from where.
Splunk, with the Splunk Add-on for Microsoft Windows:
index=<windows_index> EventCode=4726
| rename user as deleted_account, src_user as deleted_by
| table _time, dest, deleted_by, deleted_account
Microsoft Sentinel:
SecurityEvent
| where TimeGenerated > ago(1h)
| where EventID == 4726
| project TimeGenerated, Computer, DeletedBy = SubjectUserName, DeletedAccount = TargetUserName, TargetSid
Elastic, as a KQL detection rule over Windows Security events:
event.code: "4726" and winlog.channel: "Security"
The deleted account is in winlog.event_data.TargetUserName and the actor in winlog.event_data.SubjectUserName. An earlier version of this post used an Elastic Watcher that emailed on every match. A detection rule is the better fit now: it gives you alert history, suppression and a case workflow without building them yourself.
Keep the base query as a report or a dashboard panel. The three variants below are the ones worth alerting on.
Variant 1: deleted by someone who doesn't normally delete accounts
Most deletions come from a small, stable set of administrators and service accounts. Alert when the actor is not in that set.
index=<windows_index> EventCode=4726
| search NOT [| inputlookup account_admins.csv | fields src_user]
| rename user as deleted_account, src_user as deleted_by
| table _time, dest, deleted_by, deleted_account
The lookup is the tuning mechanism. Build it from 90 days of history, review it with whoever owns identity, and then keep it short.
Variant 2: bulk deletion
Deleting many accounts quickly is either a cleanup project or someone removing access on the way out. ATT&CK tracks the destructive case as T1531, Account Access Removal.
index=<windows_index> EventCode=4726
| bin _time span=10m
| stats count as deleted values(user) as deleted_accounts by _time, src_user
| where deleted >= 5
Set the threshold from your own data. If a scheduled offboarding job deletes twenty accounts every Friday, exclude that job by account and time window rather than raising the threshold for everyone.
Variant 3: created and deleted within a day
An account that exists for a few hours is rarely legitimate business. It is a common pattern for an attacker who creates an account, uses it, and removes it to clean up (T1070.009, Clear Persistence).
index=<windows_index> EventCode IN (4720, 4726) earliest=-24h
| stats min(eval(if(EventCode=4720, _time, null()))) as created
max(eval(if(EventCode=4726, _time, null()))) as deleted
values(src_user) as actors
by user
| where isnotnull(created) AND isnotnull(deleted) AND deleted > created
| eval lifetime_minutes = round((deleted - created) / 60, 1)
| convert ctime(created) ctime(deleted)
The Sentinel equivalent:
SecurityEvent
| where TimeGenerated > ago(1d)
| where EventID in (4720, 4726)
| summarize Created = minif(TimeGenerated, EventID == 4720),
Deleted = maxif(TimeGenerated, EventID == 4726),
Actors = make_set(SubjectUserName)
by TargetUserName
| where isnotnull(Created) and isnotnull(Deleted) and Deleted > Created
| extend LifetimeMinutes = datetime_diff("minute", Deleted, Created)
Expect a few hits from account provisioning mistakes that get corrected right away. Those are usually obvious from the actor and a lifetime of a minute or two.
Related events
The same three questions apply to the neighbors of 4726:
| Event ID | Meaning |
|---|---|
| 4720 | User account created |
| 4722 / 4725 | User account enabled / disabled |
| 4726 | User account deleted |
| 4743 | Computer account deleted |
| 4730 / 4734 / 4758 | Security group deleted (global / local / universal) |