Detection rules › Splunk

O365 Privileged Role Assigned To Service Principal

Status
production
Severity
medium
Group by
aws::recipientAccountId, category, dest, object_id, object_name, result, signature, src, user, vendor_product
Author
Steven Dick
Source
github.com/splunk/security_content

The following analytic detects potential privilege escalation threats in Azure Active Directory (AD). This detection is important because it identifies instances where privileged roles that hold elevated permissions are assigned to service principals. This prevents unauthorized access or malicious activities, which occur when these non-human entities access Azure resources to exploit them. False positives might occur since administrators can legitimately assign privileged roles to service principals. This detection leverages the O365 Universal Audit Log data source.

MITRE ATT&CK coverage

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body splunk

name: O365 Privileged Role Assigned To Service Principal
id: 80f3fc1b-705f-4080-bf08-f61bf013b900
version: 11
creation_date: '2024-04-13'
modification_date: '2026-05-13'
author: Steven Dick
status: production
type: TTP
description: The following analytic detects potential privilege escalation threats in Azure Active Directory (AD). This detection is important because it identifies instances where privileged roles that hold elevated permissions are assigned to service principals. This prevents unauthorized access or malicious activities, which occur when these non-human entities access Azure resources to exploit them. False positives might occur since administrators can legitimately assign privileged roles to service principals. 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_to_service_principal_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 may legitimately assign the privileged roles to Service Principals as part of administrative tasks. Filter as needed.
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
    - https://posts.specterops.io/azure-privilege-escalation-via-service-principal-abuse-210ae2be2a5
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: A privileged Azure AD role [$object_name$] was assigned to the Service Principal $user$ initiated 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 the Service Principal $user$ initiated by $src_user$
analytic_story:
    - Azure Active Directory Privilege Escalation
    - 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
tests:
    - name: True Positive Test
      attack_data:
        - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1098/o365_azure_workload_events/o365_azure_workload_events.log
          sourcetype: o365:management:activity
          source: o365
      test_type: unit

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 =
ifmatch(mvindex('Actor{}.ID', -1), "User")mvindex('Actor{}.ID', 0)
elifmatch(mvindex('Actor{}.ID', -1), "ServicePrincipal")mvindex('Actor{}.ID', 3)
elsemvindex('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
Lookup table
privileged_azure_ad_roles
Key field
azuretemplateid as object_id
Output columns
['isprvilegedadrole', '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_to_service_principal_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
Operationin
  • "Add eligible member to role."
  • "Add member to role."
Workloadeq
  • AzureActiveDirectory
categoryne
  • "User"
isprvilegedadroleeq
  • "TRUE"
sourcetypeeq
  • o365:management:activity