Detection rules › Splunk

Windows AD Object Owner Updated

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

AD Object Owner Updated. The owner provides Full control level privileges over the target AD Object. This event has significant impact alone and is also a precursor activity for hiding an AD object.

MITRE ATT&CK coverage

Event coverage

Rule body splunk

name: Windows AD Object Owner Updated
id: 4af01f6b-d8d4-4f96-8635-758a01557130
version: 12
creation_date: '2024-07-01'
modification_date: '2026-05-13'
author: Dean Luxton
status: production
type: TTP
description: AD Object Owner Updated. The owner provides Full control level privileges over the target AD Object. This event has significant impact alone and is also a precursor activity for hiding an 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 values(dest) as dest
        BY ObjectClass ObjectDN OpCorrelationID
           src_user SubjectLogonId DSName
      | rex field=old_value "O:(?P<old_owner>.*?)G:"
      | rex field=new_value "O:(?P<new_owner>.*?)G:"
      | where old_owner!=new_owner ``` optional SID resolution lookups
      | lookup identity_lookup_expanded objectSid as new_owner OUTPUT downLevelDomainName as new_owner_user
      | lookup admon_groups_def objectSid as new_owner OUTPUT cn as new_owner_group
      | lookup identity_lookup_expanded objectSid as old_owner OUTPUT downLevelDomainName as old_owner_user
      | lookup admon_groups_def objectSid as old_owner OUTPUT cn as old_owner_group ```
      | lookup builtin_groups_lookup builtin_group_string  as new_owner_group OUTPUT builtin_group_name as new_owner_group_builtin_group
      | lookup builtin_groups_lookup builtin_group_string  as old_owner OUTPUT builtin_group_name as old_owner_group_builtin_group
      | eval user=coalesce(new_owner_user, new_owner_group, new_owner_group_builtin_group, new_owner), previousOwner=coalesce(old_owner_user, old_owner_group, old_owner_group_builtin_group, old_owner)
      | stats values(previousOwner) as previousOwner values(user) as user values(SubjectLogonId) as SubjectLogonId
        BY _time ObjectClass ObjectDN
           src_user OpCorrelationID DSName
      | `windows_ad_object_owner_updated_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://learn.microsoft.com/en-us/windows/win32/secauthz/ace-strings
    - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/1522b774-6464-41a3-87a5-1e5633c3fbbb
    - https://trustedsec.com/blog/a-hitchhackers-guide-to-dacl-based-detections-part-1-a
    - 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$" and "$src_user$"
      search: '%original_detection_search% | search  user = "$user$" src_user = "$src_user$"'
      earliest_offset: $info_min_time$
      latest_offset: $info_max_time$
    - name: View risk events for the last 7 days for - "$user$" and "$src_user$"
      search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$", "$src_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: $src_user$ has made $user$ the owner of AD object $ObjectDN$
    entity:
        field: src_user
        type: user
        score: 50
intermediate_findings:
    entities:
        - field: user
          type: user
          score: 50
          message: $src_user$ has made $user$ the owner of AD object $ObjectDN$
analytic_story:
    - Sneaky Active Directory Persistence Tricks
asset_type: Endpoint
mitre_attack_id:
    - T1222.001
    - T1484
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/T1222.001/dacl_abuse/owner_updated_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 values(dest) as dest
    BY ObjectClass ObjectDN OpCorrelationID
       src_user SubjectLogonId DSName

Stage 3: rex

| rex field=old_value "O:(?P<old_owner>.*?)G:"

Stage 4: rex

| rex field=new_value "O:(?P<new_owner>.*?)G:"

Stage 5: where

| where old_owner!=new_owner

Stage 6: lookup

| lookup builtin_groups_lookup builtin_group_string  as new_owner_group OUTPUT builtin_group_name as new_owner_group_builtin_group
Lookup table
builtin_groups_lookup
Key field
builtin_group_string as new_owner_group
Output columns
['builtin_group_name', 'new_owner_group_builtin_group']

Stage 7: lookup

| lookup builtin_groups_lookup builtin_group_string  as old_owner OUTPUT builtin_group_name as old_owner_group_builtin_group
Lookup table
builtin_groups_lookup
Key field
builtin_group_string as old_owner
Output columns
['builtin_group_name', 'old_owner_group_builtin_group']

Stage 8: eval

| eval user=coalesce(new_owner_user, new_owner_group, new_owner_group_builtin_group, new_owner), previousOwner=coalesce(old_owner_user, old_owner_group, old_owner_group_builtin_group, old_owner)

Stage 9: stats

| stats values(previousOwner) as previousOwner values(user) as user values(SubjectLogonId) as SubjectLogonId
    BY _time ObjectClass ObjectDN
       src_user OpCorrelationID DSName

Stage 10: search

| `windows_ad_object_owner_updated_filter`

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)