Detection rules › Splunk

Windows AD Self DACL Assignment

Status
production
Severity
medium
Group by
LogonId, ObjectClass, ObjectDN, OpCorrelationID, _time, dest, user
Author
Dean Luxton
Source
github.com/splunk/security_content

Detect when a user creates a new DACL in AD for their own AD object.

MITRE ATT&CK coverage

Event coverage

Rule body splunk

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
drilldown_searches:
    - name: View the detection results for - "$user$"
      search: '%original_detection_search% | search  user = "$user$"'
      earliest_offset: $info_min_time$
      latest_offset: $info_max_time$
    - name: View risk events for the last 7 days for - "$user$"
      search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`'
      earliest_offset: 7d
      latest_offset: "0"
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
tests:
    - name: True Positive Test
      attack_data:
        - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1484/aclmodification/windows-security-xml.log
          source: XmlWinEventLog:Security
          sourcetype: XmlWinEventLog
      test_type: unit

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
Lookup table
ace_type_lookup
Key field
ace_type_string as aceType
Output columns
['ace_type_value', 'aceType']

Stage 11: lookup

| lookup ace_flag_lookup flag_string as aceFlags OUTPUT flag_value as ace_flag_value
Lookup table
ace_flag_lookup
Key field
flag_string as aceFlags
Output columns
['flag_value', 'ace_flag_value']

Stage 12: lookup

| lookup ace_access_rights_lookup access_rights_string as AccessRights OUTPUT access_rights_value
Lookup table
ace_access_rights_lookup
Key field
access_rights_string as AccessRights
Output columns
['access_rights_value', 'access_rights_value']

Stage 13: lookup

| lookup msad_guid_lookup guid as aceObjectGuid OUTPUT displayName as ControlAccessRights
Lookup table
msad_guid_lookup
Key field
guid as aceObjectGuid
Output columns
['displayName', 'ControlAccessRights']

Stage 14: lookup

| lookup builtin_groups_lookup builtin_group_string as aceSid OUTPUT builtin_group_name as builtin_group
Lookup table
builtin_groups_lookup
Key field
builtin_group_string as aceSid
Output columns
['builtin_group_name', '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 =
ifmvcount(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

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
new_aceeqold_values

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
EventCodeeq
  • 5136 corpus 30 (splunk 24, kusto 5, elastic 1)