Detection rules › Splunk
Windows AD Self DACL Assignment
Detect when a user creates a new DACL in AD for their own AD object.
Known false positives
- No false positives have been identified at this time.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation | |
| Defense Impairment |
Telemetry coverage
| Provider | Record / event type |
|---|---|
| Security-Auditing | Event ID 5136: A directory service object was modified. |
Rule body
name: Windows AD Self DACL Assignment
id: 16132445-da9f-4d03-ad44-56d717dcd67d
version: 12
creation_date: '2024-07-01'
modification_date: '2026-05-13'
author: Dean Luxton
status: production
type: TTP
description: Detect when a user creates a new DACL in AD for their own AD object.
data_source:
- Windows Event Log Security 5136
search: |
`wineventlog_security`
EventCode=5136
| stats min(_time) as _time
values(
eval(
if(OperationType=="%%14675",AttributeValue,null)
)
) as old_value
values(
eval(
if(OperationType=="%%14674" ,AttributeValue,null)
)
) as new_value
values(OperationType) as OperationType
by ObjectClass ObjectDN OpCorrelationID src_user SubjectLogonId dest
| rex field=old_value max_match=10000 "\((?P<old_values>.*?)\)"
| rex field=new_value max_match=10000 "\((?P<new_ace>.*?)\)"
| mvexpand new_ace
| where NOT new_ace IN (old_values)
| rex field=new_ace "(?P<aceType>.*?);(?P<aceFlags>.*?);(?P<aceAccessRights>.*?);(?P<aceObjectGuid>.*?);(?P<aceInheritedTypeGuid>.*?);(?P<aceSid>.*?)$"
| rex max_match=100 field=aceAccessRights "(?P<AccessRights>[A-Z]{2})"
| rex max_match=100 field=aceFlags "(?P<aceFlags>[A-Z]{2})"
| lookup ace_type_lookup ace_type_string as aceType OUTPUT ace_type_value as aceType
| lookup ace_flag_lookup flag_string as aceFlags OUTPUT flag_value as ace_flag_value
| lookup ace_access_rights_lookup access_rights_string as AccessRights OUTPUT access_rights_value
| lookup msad_guid_lookup guid as aceObjectGuid OUTPUT displayName as ControlAccessRights
``` Optional SID resolution lookups
| lookup identity_lookup_expanded objectSid as aceSid OUTPUT downLevelDomainName as user
| lookup admon_groups_def objectSid as aceSid OUTPUT cn as group
```
| lookup builtin_groups_lookup builtin_group_string as aceSid OUTPUT builtin_group_name as builtin_group
| eval aceType = coalesce(ace_type_value, aceType),
aceInheritance = coalesce(ace_flag_value, "This object only"),
aceAccessRights = if(
aceAccessRights = "CCDCLCSWRPWPDTLOCRSDRCWDWO", "Full control", coalesce(access_rights_value,AccessRights)
),
aceControlAccessRights = if(
(
ControlAccessRights = "Write member"
OR
aceObjectGuid = "bf9679c0-0de6-11d0-a285-00aa003049e2"
) AND
(
aceAccessRights = "All validated writes"
OR
AccessRights = "SW"
),
"Add/remove self as member",
coalesce(ControlAccessRights,aceObjectGuid)
),
user=coalesce(user, group, builtin_group, aceSid)
| stats values(aceType) as aceType
values(aceInheritance) as aceInheritance
values(aceControlAccessRights) as aceControlAccessRights
values(aceAccessRights) as aceAccessRights
values(new_ace) as new_ace
values(aceInheritedTypeGuid) as aceInheritedTypeGuid
by _time ObjectClass ObjectDN src_user SubjectLogonId user OpCorrelationID dest
| eval aceControlAccessRights = if(
mvcount(aceControlAccessRights) = 1
AND
aceControlAccessRights = "", "All rights", "aceControlAccessRights"
)
| rex field=user "\\\\(?P<nt_user>.*?)$"
| where lower(src_user)=lower(nt_user)
| `windows_ad_self_dacl_assignment_filter`
how_to_implement: Ensure you are ingesting Active Directory audit logs - specifically event 5136. See lantern article in references for further on how to onboard AD audit data. Ensure the wineventlog_security macro is configured with the correct indexes and include lookups for SID resolution if evt_resolve_ad_obj is set to 0.
known_false_positives: No false positives have been identified at this time.
references:
- https://lantern.splunk.com/Security/Product_Tips/Enterprise_Security/Enabling_an_audit_trail_from_Active_Directory
finding:
title: $user$ has created a DACL on $ObjectDN$ to grant themselves $aceControlAccessRights$ across $aceAccessRights$
entity:
field: user
type: user
score: 50
analytic_story:
- Sneaky Active Directory Persistence Tricks
asset_type: Endpoint
mitre_attack_id:
- T1484
- T1098
product:
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
category: endpoint
security_domain: endpoint
Stages and Predicates
Stage 1: search
`wineventlog_security`
EventCode=5136
Stage 2: stats
| stats min(_time) as _time
values(
eval(
if(OperationType=="%%14675",AttributeValue,null)
)
) as old_value
values(
eval(
if(OperationType=="%%14674" ,AttributeValue,null)
)
) as new_value
values(OperationType) as OperationType
by ObjectClass ObjectDN OpCorrelationID src_user SubjectLogonId dest
Stage 3: rex
| rex field=old_value max_match=10000 "\((?P<old_values>.*?)\)"
Stage 4: rex
| rex field=new_value max_match=10000 "\((?P<new_ace>.*?)\)"
Stage 5: mvexpand
| mvexpand new_ace
Stage 6: where
| where NOT new_ace IN (old_values)
Stage 7: rex
| rex field=new_ace "(?P<aceType>.*?);(?P<aceFlags>.*?);(?P<aceAccessRights>.*?);(?P<aceObjectGuid>.*?);(?P<aceInheritedTypeGuid>.*?);(?P<aceSid>.*?)$"
Stage 8: rex
| rex max_match=100 field=aceAccessRights "(?P<AccessRights>[A-Z]{2})"
Stage 9: rex
| rex max_match=100 field=aceFlags "(?P<aceFlags>[A-Z]{2})"
Stage 10: lookup
| lookup ace_type_lookup ace_type_string as aceType OUTPUT ace_type_value as aceType
Stage 11: lookup
| lookup ace_flag_lookup flag_string as aceFlags OUTPUT flag_value as ace_flag_value
Stage 12: lookup
| lookup ace_access_rights_lookup access_rights_string as AccessRights OUTPUT access_rights_value
Stage 13: lookup
| lookup msad_guid_lookup guid as aceObjectGuid OUTPUT displayName as ControlAccessRights
Stage 14: lookup
| lookup builtin_groups_lookup builtin_group_string as aceSid OUTPUT builtin_group_name as builtin_group
Stage 15: eval
| eval aceType = coalesce(ace_type_value, aceType),
aceInheritance = coalesce(ace_flag_value, "This object only"),
aceAccessRights = if(
aceAccessRights = "CCDCLCSWRPWPDTLOCRSDRCWDWO", "Full control", coalesce(access_rights_value,AccessRights)
),
aceControlAccessRights = if(
(
ControlAccessRights = "Write member"
OR
aceObjectGuid = "bf9679c0-0de6-11d0-a285-00aa003049e2"
) AND
(
aceAccessRights = "All validated writes"
OR
AccessRights = "SW"
),
"Add/remove self as member",
coalesce(ControlAccessRights,aceObjectGuid)
),
user=coalesce(user, group, builtin_group, aceSid)
Stage 16: stats
| stats values(aceType) as aceType
values(aceInheritance) as aceInheritance
values(aceControlAccessRights) as aceControlAccessRights
values(aceAccessRights) as aceAccessRights
values(new_ace) as new_ace
values(aceInheritedTypeGuid) as aceInheritedTypeGuid
by _time ObjectClass ObjectDN src_user SubjectLogonId user OpCorrelationID dest
Stage 17: eval
| eval aceControlAccessRights = if(
mvcount(aceControlAccessRights) = 1
AND
aceControlAccessRights = "", "All rights", "aceControlAccessRights"
)
aceControlAccessRights =if
mvcount(aceControlAccessRights) = 1 AND aceControlAccessRights = """All rights"else
"aceControlAccessRights"Stage 18: rex
| rex field=user "\\\\(?P<nt_user>.*?)$"
Stage 19: where
| where lower(src_user)=lower(nt_user)
Stage 20: search
| `windows_ad_self_dacl_assignment_filter`
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
new_ace | eq | old_values | excludes:new_ace field:"new_ace" value:"old_values" |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
EventCode | eq |
| field:"EventID" kind:eq value:"5136" |