Detection rules › Kusto

NRT New access credential added to Application or Service Principal

Status
available
Severity
medium
Source
github.com/Azure/Azure-Sentinel

This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where a verify KeyCredential was already present for the app. If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential. Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.

MITRE ATT&CK coverage

TacticTechniques
Lateral Movement

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

id: e42e889a-caaf-4dbb-aec6-371b37d64298
name: NRT New access credential added to Application or Service Principal
description: |
  'This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where a verify KeyCredential was already present for the app.
  If a threat actor obtains access to an account with sufficient privileges and adds the alternate authentication material triggering this event, the threat actor can now authenticate as the Application or Service Principal using this credential.
  Additional information on OAuth Credential Grants can be found in RFC 6749 Section 4.4 or https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
  For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1550
tags:
  - Solorigate
  - NOBELIUM
query: |
  AuditLogs
  | where OperationName has_any ("Add service principal", "Certificates and secrets management") // captures "Add service principal", "Add service principal credentials", and "Update application - Certificates and secrets management" events
  | where Result =~ "success"
  | where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"
  | mv-apply TargetResource = TargetResources on 
    (
        where TargetResource.type =~ "Application"
        | extend targetDisplayName = tostring(TargetResource.displayName),
                 targetId = tostring(TargetResource.id),
                 targetType = tostring(TargetResource.type),
                 keyEvents = TargetResource.modifiedProperties
    )
  | mv-apply Property = keyEvents on 
    (
        where Property.displayName =~ "KeyDescription"
        | extend new_value_set = parse_json(tostring(Property.newValue)),
                 old_value_set = parse_json(tostring(Property.oldValue))
    )
  | where old_value_set != "[]"
  | extend diff = set_difference(new_value_set, old_value_set)
  | where diff != "[]"
  | parse diff with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *
  | where keyUsage =~ "Verify"
  | mv-apply AdditionalDetail = AdditionalDetails on 
    (
        where AdditionalDetail.key =~ "User-Agent"
        | extend UserAgent = tostring(AdditionalDetail.value)
    )
  | 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(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))
  // The below line is currently commented out but Microsoft Sentinel users can modify this query to show only Application or only Service Principal events in their environment
  //| where targetType =~ "Application" // or targetType =~ "ServicePrincipal"
  | project-away diff, new_value_set, old_value_set
  | project-reorder TimeGenerated, OperationName, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId
  | extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: InitiatingUserPrincipalName
      - identifier: Name
        columnName: InitiatedByName
      - identifier: UPNSuffix
        columnName: InitiatedByUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: InitiatingAadUserId
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: InitiatingAppName
      - identifier: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIpAddress
version: 1.0.5
kind: NRT

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName has_any ("Add service principal", "Certificates and secrets management")

Stage 3: where

| where Result =~ "success"

Stage 4: where

| where tostring(InitiatedBy.user.userPrincipalName) has "@" or tostring(InitiatedBy.app.displayName) has "@"

Stage 5: kusto:mv-apply

| mv-apply TargetResource = TargetResources on 
  (
      where TargetResource.type =~ "Application"
      | extend targetDisplayName = tostring(TargetResource.displayName),
               targetId = tostring(TargetResource.id),
               targetType = tostring(TargetResource.type),
               keyEvents = TargetResource.modifiedProperties
  )

Stage 6: kusto:mv-apply

| mv-apply Property = keyEvents on 
  (
      where Property.displayName =~ "KeyDescription"
      | extend new_value_set = parse_json(tostring(Property.newValue)),
               old_value_set = parse_json(tostring(Property.oldValue))
  )

Stage 7: where

| where old_value_set != "[]"

Stage 8: extend

| extend diff = set_difference(new_value_set, old_value_set)

Stage 9: where

| where diff != "[]"

Stage 10: parse

| parse diff with * "KeyIdentifier=" keyIdentifier:string ",KeyType=" keyType:string ",KeyUsage=" keyUsage:string ",DisplayName=" keyDisplayName:string "]" *

Stage 11: where

| where keyUsage =~ "Verify"

Stage 12: kusto:mv-apply

| mv-apply AdditionalDetail = AdditionalDetails on 
  (
      where AdditionalDetail.key =~ "User-Agent"
      | extend UserAgent = tostring(AdditionalDetail.value)
  )

Stage 13: extend (5 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(iff(isnotempty(InitiatedBy.user.ipAddress), InitiatedBy.user.ipAddress, InitiatedBy.app.ipAddress))

Stage 14: project-away

| project-away diff, new_value_set, old_value_set

Stage 15: project-reorder

| project-reorder TimeGenerated, OperationName, InitiatingUserPrincipalName, InitiatingAadUserId, InitiatingAppName, InitiatingAppServicePrincipalId, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId

Stage 16: extend

| extend InitiatedByName = tostring(split(InitiatingUserPrincipalName,'@',0)[0]), InitiatedByUPNSuffix = tostring(split(InitiatingUserPrincipalName,'@',1)[0])

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
InitiatedByNameextend
InitiatedByUPNSuffixextend