Detection rules › Kusto

BTP - Cloud Identity Service application configuration monitor

Status
available
Severity
medium
Time window
15m
Source
github.com/Azure/Azure-Sentinel

Identifies CRUD operations on Application (SSO Domain/Service Provider) configurations within SAP Cloud Identity Service. This includes both SAML 2.0 and OpenID Connect applications. Unauthorized application creation could indicate an attacker establishing persistent access through a rogue federated application.

MITRE ATT&CK coverage

Rule body kusto

id: 3f8a2c5e-7b9d-4e1a-8f6c-2d4b9a1e3c7f
kind: Scheduled
name: BTP - Cloud Identity Service application configuration monitor
description: |
  Identifies CRUD operations on Application (SSO Domain/Service Provider) configurations
  within SAP Cloud Identity Service. This includes both SAML 2.0 and OpenID Connect applications.
  Unauthorized application creation could indicate an attacker establishing persistent access
  through a rogue federated application.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: SAPBTPAuditEvents
    dataTypes:
      - SAPBTPAuditLog_CL
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
  - PrivilegeEscalation
relevantTechniques:
  - T1606
  - T1556
  - T1134
query: |
  SAPBTPAuditLog_CL
  | extend data_s = tostring(Message.data)
  | extend 
      action = extract(@"action=""([^""]+)""", 1, data_s),
      state = extract(@"state=""([^""]+)""", 1, data_s),
      objectType = extract(@"objectType=""([^""]+)""", 1, data_s),
      serviceProviderName = extract(@"serviceProviderName=""([^""]+)""", 1, data_s),
      ipAddress = extract(@"ipAddress=""([^""]+)""", 1, data_s),
      // Extract payload JSON for federation type
      payloadJson = extract(@"\{""payload"":\{([^}]+)\}\}", 1, data_s)
  | extend federationType = extract(@"""type"":""([^""]+)""", 1, payloadJson)
  | where objectType == "ssoDomain"
  | where state == "successful"
  | where action in ("create", "update", "delete")
  | extend MessageText = case(
      action == "create", strcat("Application '", serviceProviderName, "' (", federationType, ") was created"),
      action == "update", strcat("Application '", serviceProviderName, "' (", federationType, ") was updated"),
      action == "delete", strcat("Application '", serviceProviderName, "' was deleted"),
      "Unclassified operation on application"
  )
  | project
      UpdatedOn,
      UserName,
      MessageText,
      ServiceProviderName = serviceProviderName,
      FederationType = federationType,
      Action = action,
      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: IP
    fieldMappings:
      - identifier: Address
        columnName: ipAddress
  - entityType: CloudApplication
    fieldMappings:
      - identifier: Name
        columnName: CloudApp
alertDetailsOverride:
  alertDisplayNameFormat: 'SAP Cloud Identity Service: {{MessageText}}'
  alertDescriptionFormat: |
    {{MessageText}} by {{UserName}}. Identity provider name: {{ServiceProviderName}}
        
    This could indicate:
    - Legitimate administrative change to federated applications
    - Unauthorized application registration for persistent access
    - Rogue SAML/OIDC application added by attacker
customDetails:
  ServiceProviderName: ServiceProviderName
  FederationType: FederationType
  Action: Action
  SourceIP: ipAddress
version: 1.0.0

Stages and Predicates

Stage 1: source

SAPBTPAuditLog_CL

Stage 2: extend (3 consecutive steps)

| extend data_s = tostring(Message.data)
| extend 
    action = extract(@"action=""([^""]+)""", 1, data_s),
    state = extract(@"state=""([^""]+)""", 1, data_s),
    objectType = extract(@"objectType=""([^""]+)""", 1, data_s),
    serviceProviderName = extract(@"serviceProviderName=""([^""]+)""", 1, data_s),
    ipAddress = extract(@"ipAddress=""([^""]+)""", 1, data_s),
    payloadJson = extract(@"\{""payload"":\{([^}]+)\}\}", 1, data_s)
| extend federationType = extract(@"""type"":""([^""]+)""", 1, payloadJson)

Stage 3: where

| where objectType == "ssoDomain"

Stage 4: where

| where state == "successful"

Stage 5: where

| where action in ("create", "update", "delete")

Stage 6: extend

| extend MessageText = case(
    action == "create", strcat("Application '", serviceProviderName, "' (", federationType, ") was created"),
    action == "update", strcat("Application '", serviceProviderName, "' (", federationType, ") was updated"),
    action == "delete", strcat("Application '", serviceProviderName, "' was deleted"),
    "Unclassified operation on application"
)
MessageText =
ifaction == "create"strcat("Application '", serviceProviderName, "' (", federationType, ") was created")
elifaction == "update"strcat("Application '", serviceProviderName, "' (", federationType, ") was updated")
elifaction == "delete"strcat("Application '", serviceProviderName, "' was deleted")
else"Unclassified operation on application"

Stage 7: project

| project
    UpdatedOn,
    UserName,
    MessageText,
    ServiceProviderName = serviceProviderName,
    FederationType = federationType,
    Action = action,
    Tenant,
    ipAddress,
    CloudApp = "SAP Cloud Identity Service"

Stage 8: 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
actionin
  • create transforms: cased
  • delete transforms: cased
  • update transforms: cased
objectTypeeq
  • ssoDomain transforms: cased
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
Actionproject
CloudAppproject
FederationTypeproject
MessageTextproject
ServiceProviderNameproject
Tenantproject
UpdatedOnproject
UserNameproject
ipAddressproject
AccountNameextend
UPNSuffixextend