Detection rules › Splunk
O365 Privileged Role Assigned
The following analytic identifies the assignment of sensitive and privileged Azure Active Directory roles to an Azure AD user. Adversaries and red teams alike may assign these roles to a compromised account to establish Persistence in an Azure AD environment. This detection leverages the O365 Universal Audit Log data source.
Known false positives
- Administrators will legitimately assign the privileged roles users as part of administrative tasks. Microsoft Privileged Identity Management (PIM) may cause false positives / less accurate alerting.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation |
Telemetry coverage
| Platform | Record / event type |
|---|---|
| Microsoft 365 | Add eligible member to role. |
| Microsoft 365 | Add member to role. |
Rules detecting the same action
These rules filter on the same operation.
Rule body
name: O365 Privileged Role Assigned
id: db435700-4ddc-4c23-892e-49e7525d7d39
version: 11
creation_date: '2024-04-13'
modification_date: '2026-05-13'
author: Steven Dick
status: production
type: TTP
description: The following analytic identifies the assignment of sensitive and privileged Azure Active Directory roles to an Azure AD user. Adversaries and red teams alike may assign these roles to a compromised account to establish Persistence in an Azure AD environment. This detection leverages the O365 Universal Audit Log data source.
data_source:
- Office 365 Universal Audit Log
search: "`o365_management_activity` Workload=AzureActiveDirectory Operation IN (\"Add member to role.\",\"Add eligible member to role.\") | eval user = ObjectId, src_user = case(match(mvindex('Actor{}.ID',-1),\"User\"),mvindex('Actor{}.ID',0),match(mvindex('Actor{}.ID',-1),\"ServicePrincipal\"),mvindex('Actor{}.ID',3),true(),mvindex('Actor{}.ID',0)), object_name = mvindex('ModifiedProperties{}.NewValue', mvfind('ModifiedProperties{}.Name',\"Role\\.DisplayName\")), object_id = mvindex('ModifiedProperties{}.NewValue', mvfind('ModifiedProperties{}.Name',\"Role\\.TemplateId\")), signature = Operation, result = ResultStatus, category = mvindex('Target{}.ID',2) | fillnull | stats count, min(_time) as firstTime, max(_time) as lastTime by src_user, src, user, category, result, object_name, object_id, signature, vendor_account, vendor_product, dest | lookup privileged_azure_ad_roles azuretemplateid as object_id OUTPUT isprvilegedadrole | search isprvilegedadrole=\"TRUE\" category=\"User\" | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `o365_privileged_role_assigned_filter`"
how_to_implement: You must install the Splunk Microsoft Office 365 Add-on and ingest Office 365 management activity events.
known_false_positives: Administrators will legitimately assign the privileged roles users as part of administrative tasks. Microsoft Privileged Identity Management (PIM) may cause false positives / less accurate alerting.
references:
- https://attack.mitre.org/techniques/T1098/003/
- https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference
- https://learn.microsoft.com/en-us/microsoft-365/admin/add-users/about-exchange-online-admin-role?view=o365-worldwide
finding:
title: A privileged Azure AD role [$object_name$] was assigned to user $user$ by $src_user$
entity:
field: src_user
type: user
score: 50
intermediate_findings:
entities:
- field: user
type: user
score: 50
message: A privileged Azure AD role [$object_name$] was assigned to user $user$ by $src_user$
analytic_story:
- Azure Active Directory Persistence
- Scattered Lapsus$ Hunters
asset_type: O365 Tenant
mitre_attack_id:
- T1098.003
product:
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
category: cloud
security_domain: identity
Stages and Predicates
Stage 1: search
`o365_management_activity` Workload=AzureActiveDirectory Operation IN ("Add member to role.","Add eligible member to role.")
Stage 2: eval
| eval user = ObjectId, src_user = case(match(mvindex('Actor{}.ID',-1),"User"),mvindex('Actor{}.ID',0),match(mvindex('Actor{}.ID',-1),"ServicePrincipal"),mvindex('Actor{}.ID',3),true(),mvindex('Actor{}.ID',0)), object_name = mvindex('ModifiedProperties{}.NewValue', mvfind('ModifiedProperties{}.Name',"Role\.DisplayName")), object_id = mvindex('ModifiedProperties{}.NewValue', mvfind('ModifiedProperties{}.Name',"Role\.TemplateId")), signature = Operation, result = ResultStatus, category = mvindex('Target{}.ID',2)
src_user =if
match(mvindex('Actor{}.ID', -1), "User")mvindex('Actor{}.ID', 0)elif
match(mvindex('Actor{}.ID', -1), "ServicePrincipal")mvindex('Actor{}.ID', 3)else
mvindex('Actor{}.ID', 0)Stage 3: fillnull
| fillnull
Stage 4: stats
| stats count, min(_time) as firstTime, max(_time) as lastTime by src_user, src, user, category, result, object_name, object_id, signature, vendor_account, vendor_product, dest
Stage 5: lookup
| lookup privileged_azure_ad_roles azuretemplateid as object_id OUTPUT isprvilegedadrole
Stage 6: search
| search isprvilegedadrole="TRUE" category="User"
Stage 7: search
| `security_content_ctime(firstTime)`
Stage 8: search
| `security_content_ctime(lastTime)`
Stage 9: search
| `o365_privileged_role_assigned_filter`
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
Operation | in |
| field:"Operation" kind:in |
Workload | eq |
| field:"Workload" kind:eq value:"AzureActiveDirectory" |
category | eq |
| field:"category" kind:eq |
isprvilegedadrole | eq |
| field:"isprvilegedadrole" kind:eq |
sourcetype | eq |
| field:"sourcetype" kind:eq value:"o365:management:activity" |