Service principal credential addition by non-historical actor


Description

Identifies service principal credential additions or updates by actors with no history of this operation in the preceding 90 days. A new actor outside the established baseline may indicate credential abuse by a compromised account.

Query · kql

let timeframe = 1d;
let lookback   = 90d;
let KnownActors =
    AuditLogs
    | where TimeGenerated >= ago(timeframe + lookback) and TimeGenerated < ago(timeframe)
    | where OperationName in~ (
          "Add service principal credentials",
          "Update application - Certificates and secrets management"
      )
    | where Result =~ "success"
    | extend Actor = iff(
          isnotempty(tostring(InitiatedBy.user.userPrincipalName)),
          tostring(InitiatedBy.user.userPrincipalName),
          tostring(InitiatedBy.app.displayName))
    | where isnotempty(Actor)
    | distinct Actor;
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ (
      "Add service principal credentials",
      "Update application - Certificates and secrets management"
  )
| where Result =~ "success"
| extend ActorUpn  = tostring(InitiatedBy.user.userPrincipalName)
| extend ActorApp  = tostring(InitiatedBy.app.displayName)
| extend Actor     = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| extend ActorIp   = iff(
      isnotempty(tostring(InitiatedBy.user.ipAddress)),
      tostring(InitiatedBy.user.ipAddress),
      tostring(InitiatedBy.app.ipAddress))
| extend TargetSP   = tostring(TargetResources[0].displayName)
| extend TargetSpId = tostring(TargetResources[0].id)
| where isnotempty(Actor)
| join kind=leftanti KnownActors on Actor
| extend AccountName      = iff(Actor has "@", tostring(split(Actor, "@")[0]), Actor)
| extend AccountUPNSuffix = iff(Actor has "@", tostring(split(Actor, "@")[1]), "")
| project TimeGenerated, TargetSP, TargetSpId, Actor, AccountName,
          AccountUPNSuffix, ActorIp, OperationName, CorrelationId
| sort by TimeGenerated desc
Raw source Service principal credential addition by non-historical actor · KQL
Esc
Published by Azure/Azure-Sentinel ↗, licensed under MIT ↗. Reproduced here unmodified.
id: 4519bc3b-1849-4f37-b98b-6e8d67b34c71
name: Service principal credential addition by non-historical actor
description: |
  Identifies service principal credential additions or updates by actors with no history
  of this operation in the preceding 90 days. A new actor outside the established
  baseline may indicate credential abuse by a compromised account.
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
tactics:
  - Persistence
relevantTechniques:
  - T1098.001
query: |
  let timeframe = 1d;
  let lookback   = 90d;
  let KnownActors =
      AuditLogs
      | where TimeGenerated >= ago(timeframe + lookback) and TimeGenerated < ago(timeframe)
      | where OperationName in~ (
            "Add service principal credentials",
            "Update application - Certificates and secrets management"
        )
      | where Result =~ "success"
      | extend Actor = iff(
            isnotempty(tostring(InitiatedBy.user.userPrincipalName)),
            tostring(InitiatedBy.user.userPrincipalName),
            tostring(InitiatedBy.app.displayName))
      | where isnotempty(Actor)
      | distinct Actor;
  AuditLogs
  | where TimeGenerated >= ago(timeframe)
  | where OperationName in~ (
        "Add service principal credentials",
        "Update application - Certificates and secrets management"
    )
  | where Result =~ "success"
  | extend ActorUpn  = tostring(InitiatedBy.user.userPrincipalName)
  | extend ActorApp  = tostring(InitiatedBy.app.displayName)
  | extend Actor     = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
  | extend ActorIp   = iff(
        isnotempty(tostring(InitiatedBy.user.ipAddress)),
        tostring(InitiatedBy.user.ipAddress),
        tostring(InitiatedBy.app.ipAddress))
  | extend TargetSP   = tostring(TargetResources[0].displayName)
  | extend TargetSpId = tostring(TargetResources[0].id)
  | where isnotempty(Actor)
  | join kind=leftanti KnownActors on Actor
  | extend AccountName      = iff(Actor has "@", tostring(split(Actor, "@")[0]), Actor)
  | extend AccountUPNSuffix = iff(Actor has "@", tostring(split(Actor, "@")[1]), "")
  | project TimeGenerated, TargetSP, TargetSpId, Actor, AccountName,
            AccountUPNSuffix, ActorIp, OperationName, CorrelationId
  | sort by TimeGenerated desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Actor
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ActorIp
version: 1.0.0
metadata:
    source:
        kind: Community
    author:
        name: descambiado
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection", "Identity" ]

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.