A new service principal credential addition by an actor with no prior history of this activity may indicate an adversary attempting to establish persistent access. SOC teams should proactively hunt for this behavior in Azure Sentinel to detect potential credential compromise and unauthorized access attempts.
KQL 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
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" ]
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
Scenario: Scheduled Job Credential Rotation
Description: A scheduled job (e.g., Azure Automation Runbook) runs a script to rotate credentials for a service principal, which is a routine maintenance task.
Filter/Exclusion: Exclude activities associated with known automation tools like Azure Automation, PowerShell scripts with Az module, or tasks with job_name matching standard rotation patterns.
Scenario: Admin Task to Update Service Principal Credentials
Description: An admin manually updates a service principal’s credentials via the Azure portal or CLI as part of a security policy change.
Filter/Exclusion: Exclude users with admin privileges (e.g., [email protected]) or activities that occur during known maintenance windows or security policy updates.
Scenario: Third-Party Tool Integration
Description: A third-party tool (e.g., Okta, Azure AD Connect) automatically updates a service principal’s credentials during synchronization or integration setup.
Filter/Exclusion: Exclude activities originating from known integration tools or services like Okta, Azure AD Connect, or Microsoft Entra ID Sync.
Scenario: Development Environment Credential Sync
Description: A development team uses a CI/CD pipeline (e.g., Jenkins, GitHub Actions) to sync service principal credentials between environments.
Filter/Exclusion: Exclude activities from development environments or CI/CD pipelines (e.g., [email protected], [email protected]) or IP ranges associated with development infrastructure.
Scenario: Temporary User for One-Time Credential Update
Description: A temporary user (e.g., [email protected]) is created to perform a one-time credential update for a service principal.
Filter/Exclusion: Exclude users with temporary or short-lived credentials, or users that match a predefined list of temporary admin accounts.