Detection rules › Kusto

NRT First access credential added to Application or Service Principal where no credential was present

Severity
medium
Author
Samik Roy
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 there was no previous verify KeyCredential associated. 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

Event coverage

Rule body kusto

id: b6988c32-4f3b-4a45-8313-b46b33061a74
name: NRT First access credential added to Application or Service Principal where no credential was present
description: |
  'This will alert when an admin or app owner account adds a new credential to an Application or Service Principal where there was no previous verify KeyCredential associated.
  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
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1550.001
tags:
  - Solorigate
  - NOBELIUM
query: |
  AuditLogs
  | where OperationName has_any ("Add service principal", "Certificates and secrets management")
  | 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 == "[]"
  | mv-expand new_value_set
  | parse new_value_set 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))
  //| where targetType =~ "Application" // or targetType =~ "ServicePrincipal"
  | project-away 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: AadUserId
        columnName: InitiatingAppServicePrincipalId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: InitiatingIpAddress
version: 1.0.6
kind: NRT
metadata:
source:
    kind: Community
author:
    name: Samik Roy
support:
    tier: Community
categories:
    domains: [ "Security - Others", "Identity" ]
threatAnalysisTactics: [ "DefenseEvasion" ]
threatAnalysisTechniques: [ "T1550.001" ]

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: mv-expand

| mv-expand new_value_set

Stage 9: parse

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

Stage 10: where

| where keyUsage == "Verify"

Stage 11: kusto:mv-apply

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

Stage 12: 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 13: project-away

| project-away new_value_set, old_value_set

Stage 14: project-reorder

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

Stage 15: extend

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

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
OperationNamematch
  • Add service principal
  • Certificates and secrets management
Resulteq
  • success
displayNameeq
  • KeyDescription
displayNamematch
  • @ transforms: tostring, term
keyeq
  • User-Agent
keyUsageeq
  • Verify transforms: cased
old_value_seteq
  • [] transforms: cased
typeeq
  • Application
userPrincipalNamematch
  • @ transforms: tostring, term

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
InitiatingAppNameextend
InitiatingAppServicePrincipalIdextend
InitiatingUserPrincipalNameextend
InitiatingAadUserIdextend
InitiatingIpAddressextend
InitiatedByNameextend
InitiatedByUPNSuffixextend