Detection rules › Kusto

Changes to Application Ownership

Severity
medium
Time window
2h
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Detects changes to the ownership of an appplicaiton. Monitor these changes to make sure that they were authorized. Ref: https://learn.microsoft.com/en-gb/entra/architecture/security-operations-applications#new-owner

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
AzureAdd owner to application

Rules detecting the same action

These rules filter on the same operation.

Rule body

id: cc5780ce-3245-4bba-8bc1-e9048c2257ce
name: Changes to Application Ownership
description: |
  'Detects changes to the ownership of an appplicaiton.
    Monitor these changes to make sure that they were authorized.
    Ref: https://learn.microsoft.com/en-gb/entra/architecture/security-operations-applications#new-owner'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 2h
queryPeriod: 2h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Persistence
  - PrivilegeEscalation
relevantTechniques:
  - T1078.004
tags:
  - AADSecOpsGuide
query: |
  AuditLogs
    | where Category =~ "ApplicationManagement"
    | where OperationName =~ "Add owner to application"
    | extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
    | extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
    | extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
    | extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
    | extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
    | extend TargetUserPrincipalName = TargetResources[0].userPrincipalName
    | extend TargetAadUserId = tostring(TargetResources[0].id)
    | extend mod_props = TargetResources[0].modifiedProperties
    | mv-expand mod_props
    | where mod_props.displayName =~ "Application.DisplayName"
    | extend TargetAppName = tostring(parse_json(tostring(mod_props.newValue)))
    | extend AddedUser = TargetUserPrincipalName
    | extend UpdatedBy = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
    | extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
    | extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])
    | project-reorder TimeGenerated, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingAadUserId, InitiatingUserPrincipalName, InitiatingIPAddress, TargetAppName, AddedUser, UpdatedBy
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatingAccountName
      - identifier: UPNSuffix
        columnName: InitiatingAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetUserPrincipalName
      - identifier: Name
        columnName: TargetAccountName
      - identifier: UPNSuffix
        columnName: TargetAccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: TargetAadUserId
version: 1.1.2
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where Category =~ "ApplicationManagement"

Stage 3: where

| where OperationName =~ "Add owner to application"

Stage 4: extend (8 consecutive steps)

| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingAppServicePrincipalId = tostring(InitiatedBy.app.servicePrincipalId)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend InitiatingAadUserId = tostring(InitiatedBy.user.id)
| extend InitiatingIPAddress = tostring(InitiatedBy.user.ipAddress)
| extend TargetUserPrincipalName = TargetResources[0].userPrincipalName
| extend TargetAadUserId = tostring(TargetResources[0].id)
| extend mod_props = TargetResources[0].modifiedProperties

Stage 5: mv-expand

| mv-expand mod_props

Stage 6: where

| where mod_props.displayName =~ "Application.DisplayName"

Stage 7: extend (5 consecutive steps)

| extend TargetAppName = tostring(parse_json(tostring(mod_props.newValue)))
| extend AddedUser = TargetUserPrincipalName
| extend UpdatedBy = iif(isnotempty(InitiatingAppName), InitiatingAppName, InitiatingUserPrincipalName)
| extend InitiatingAccountName = tostring(split(InitiatingUserPrincipalName, "@")[0]), InitiatingAccountUPNSuffix = tostring(split(InitiatingUserPrincipalName, "@")[1])
| extend TargetAccountName = tostring(split(TargetUserPrincipalName, "@")[0]), TargetAccountUPNSuffix = tostring(split(TargetUserPrincipalName, "@")[1])

Stage 8: project-reorder

| project-reorder TimeGenerated, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingAadUserId, InitiatingUserPrincipalName, InitiatingIPAddress, TargetAppName, AddedUser, UpdatedBy

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIPAddressextend
TargetUserPrincipalNameextend
TargetAadUserIdextend
mod_propsextend
TargetAppNameextend
AddedUserextend
UpdatedByextend
InitiatingAccountNameextend
InitiatingAccountUPNSuffixextend
TargetAccountNameextend
TargetAccountUPNSuffixextend