Detection rules › Kusto

BTP - User added to Cloud Identity Service privileged Administrators list

Status
available
Severity
high
Time window
15m
Group by
Tenant, UpdatedOn, UserName, callerMail, ipAddress, targetUser
Source
github.com/Azure/Azure-Sentinel

Identifies when a user is granted privileged administrator permissions in SAP Cloud Identity Service. These permissions include managing Identity Providers, Service Providers, Users, Groups, and Access controls.

MITRE ATT&CK coverage

TacticTechniques
Privilege EscalationT1078 Valid Accounts
PersistenceT0859 Valid Accounts
Lateral MovementT0859 Valid Accounts

Rule body kusto

id: 7d4e9f2a-8b1c-4a5d-9e3f-6c2b1a0d8e7f
kind: Scheduled
name: BTP - User added to Cloud Identity Service privileged Administrators list
description: Identifies when a user is granted privileged administrator permissions
  in SAP Cloud Identity Service. These permissions include managing Identity Providers,
  Service Providers, Users, Groups, and Access controls.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SAPBTPAuditEvents
    dataTypes:
      - SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - LateralMovement
  - PrivilegeEscalation
relevantTechniques:
  - T0859
  - T1078
query: |
  let monitored_permissions = dynamic(["ManageIdP", "ManageSP", "ManageUsers", "ReadUsers", "ManageAccess", "ManageGroups"]);
  SAPBTPAuditLog_CL
  | extend data_s = tostring(Message.data)
  | extend action = extract(@"action=""([^""]+)""", 1, data_s),
           state = extract(@"state=""([^""]+)""", 1, data_s),
           changedAttribute = extract(@"changedAttribute=""([^""]+)""", 1, data_s),
           newValue = extract(@"newValue=""([^""]+)""", 1, data_s),
           targetUser = extract(@"userIdentifier=""([^""]+)""", 1, data_s),
           callerMail = extract(@"callerMail=""([^""]+)""", 1, data_s),
           ipAddress = extract(@"ipAddress=""([^""]+)""", 1, data_s)
  | where action == "grantPermissions"
  | where state == "successful"
  | where changedAttribute == "authorizations"
  | where isnotempty(newValue)
  | mv-expand permission = monitored_permissions
  | where newValue contains tostring(permission)
  | summarize 
      GrantedPermissions = make_set(newValue, 10),
      MatchedPermissions = make_set(tostring(permission), 10)
      by UpdatedOn, UserName, callerMail, targetUser, Tenant, ipAddress
  | project UpdatedOn, UserName, callerMail, targetUser, GrantedPermissions, MatchedPermissions, Tenant, ipAddress, CloudApp = "SAP Cloud Identity Service"
  | extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: CloudApp
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ipAddress
version: 1.0.0

Stages and Predicates

Parameters

let monitored_permissions = dynamic(["ManageIdP", "ManageSP", "ManageUsers", "ReadUsers", "ManageAccess", "ManageGroups"]);

Stage 1: source

SAPBTPAuditLog_CL

Stage 2: extend

| extend data_s = tostring(Message.data)

Stage 3: extend

| extend action = extract(@"action=""([^""]+)""", 1, data_s),
         state = extract(@"state=""([^""]+)""", 1, data_s),
         changedAttribute = extract(@"changedAttribute=""([^""]+)""", 1, data_s),
         newValue = extract(@"newValue=""([^""]+)""", 1, data_s),
         targetUser = extract(@"userIdentifier=""([^""]+)""", 1, data_s),
         callerMail = extract(@"callerMail=""([^""]+)""", 1, data_s),
         ipAddress = extract(@"ipAddress=""([^""]+)""", 1, data_s)

Stage 4: where

| where action == "grantPermissions"

Stage 5: where

| where state == "successful"

Stage 6: where

| where changedAttribute == "authorizations"

Stage 7: where

| where isnotempty(newValue)

Stage 8: mv-expand

| mv-expand permission = monitored_permissions

Stage 9: where

| where newValue contains tostring(permission)

Stage 10: summarize

| summarize 
    GrantedPermissions = make_set(newValue, 10),
    MatchedPermissions = make_set(tostring(permission), 10)
    by UpdatedOn, UserName, callerMail, targetUser, Tenant, ipAddress

Stage 11: project

| project UpdatedOn, UserName, callerMail, targetUser, GrantedPermissions, MatchedPermissions, Tenant, ipAddress, CloudApp = "SAP Cloud Identity Service"

Stage 12: extend

| extend AccountName = split(UserName, "@")[0], UPNSuffix = split(UserName, "@")[1]

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
actioneq
  • grantPermissions transforms: cased
changedAttributeeq
  • authorizations transforms: cased
newValuecontains
  • permission
newValueis_not_null
  • (no value, null check)
stateeq
  • successful transforms: cased

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
CloudAppproject
GrantedPermissionsproject
MatchedPermissionsproject
Tenantproject
UpdatedOnproject
UserNameproject
callerMailproject
ipAddressproject
targetUserproject
AccountNameextend
UPNSuffixextend