A rarely observed actor is adding or updating credentials for service principals or applications, which may indicate unauthorized or malicious credential management activity. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential lateral movement or persistence tactics by unknown actors.
KQL Query
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let baselineLookback = starttime - 90d;
let credOps = dynamic([
"Add service principal credentials",
"Update application - Certificates and secrets management"
]);
// Build 90-day baseline of actors who have previously performed credential operations
let KnownActors =
AuditLogs
| where TimeGenerated between (baselineLookback .. starttime)
| where OperationName in~ (credOps)
| extend ActorUpn = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend ActorApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| where isnotempty(Actor)
| summarize by Actor;
// Credential operations in the current window
AuditLogs
| where TimeGenerated between (starttime .. endtime)
| where OperationName in~ (credOps)
| extend ActorUpn = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend ActorApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend ActorIp = iff(
isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)),
tostring(parse_json(tostring(InitiatedBy.user)).ipAddress),
tostring(parse_json(tostring(InitiatedBy.app)).ipAddress))
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| where isnotempty(Actor)
// Keep only actors not seen in the 90-day baseline
| join kind=leftanti KnownActors on Actor
| extend TargetDisplayName = tostring(TargetResources[0].displayName)
| extend TargetId = tostring(TargetResources[0].id)
| extend TargetType = tostring(TargetResources[0].type)
| extend AccountName = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[0]), Actor)
| extend AccountUPNSuffix = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[1]), "")
| project
TimeGenerated,
OperationName,
Actor,
AccountName,
AccountUPNSuffix,
ActorApp,
ActorIp,
TargetDisplayName,
TargetId,
TargetType,
CorrelationId,
Result
| sort by TimeGenerated desc
id: 138381e3-95d5-4d21-ab0b-13f941b82acc
name: Service principal or application credential addition by a rarely observed actor
description: |
Hunting query that looks for credential additions or updates on service principals and
applications performed by actors (users or apps) that have not been observed initiating
the same operations in the previous 90 days. Covered operations are
"Add service principal credentials" and "Update application - Certificates and secrets
management", which correspond to adding passwordCredentials or keyCredentials to an
existing registration. A rarely observed actor performing these operations can indicate
persistence activity, such as a compromised account adding a backdoor credential to a
high-privilege application.
This query is hypothesis-driven and requires analyst validation. Benign matches include
newly onboarded administrators, infrastructure-as-code pipelines running for the first
time, and certificate rotation performed by a different operator than usual.
References:
- https://learn.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities
- https://attack.mitre.org/techniques/T1098/001/
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- Persistence
relevantTechniques:
- T1098.001
query: |
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let baselineLookback = starttime - 90d;
let credOps = dynamic([
"Add service principal credentials",
"Update application - Certificates and secrets management"
]);
// Build 90-day baseline of actors who have previously performed credential operations
let KnownActors =
AuditLogs
| where TimeGenerated between (baselineLookback .. starttime)
| where OperationName in~ (credOps)
| extend ActorUpn = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend ActorApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| where isnotempty(Actor)
| summarize by Actor;
// Credential operations in the current window
AuditLogs
| where TimeGenerated between (starttime .. endtime)
| where OperationName in~ (credOps)
| extend ActorUpn = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend ActorApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend ActorIp = iff(
isnotempty(tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)),
tostring(parse_json(tostring(InitiatedBy.user)).ipAddress),
tostring(parse_json(tostring(InitiatedBy.app)).ipAddress))
| extend Actor = iff(isnotempty(ActorUpn), ActorUpn, ActorApp)
| where isnotempty(Actor)
// Keep only actors not seen in the 90-day baseline
| join kind=leftanti KnownActors on Actor
| extend TargetDisplayName = tostring(TargetResources[0].d
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
Scenario: A system administrator adds a new service principal for a third-party monitoring tool (e.g., Datadog or New Relic) using the Azure portal.
Filter/Exclusion: Exclude activities related to known monitoring and observability tools (e.g., tool_name = 'Datadog' OR tool_name = 'New Relic').
Scenario: A scheduled job (e.g., Azure DevOps pipeline or Logic App) automatically updates an application’s credentials during a CI/CD deployment.
Filter/Exclusion: Exclude activities initiated by known CI/CD tools (e.g., caller = 'Azure DevOps' OR caller = 'Logic App').
Scenario: An admin uses PowerShell to bulk update service principal credentials for multiple applications during a routine maintenance window.
Filter/Exclusion: Exclude activities where the caller is a known administrative tool (e.g., caller = 'PowerShell' and action = 'bulk update').
Scenario: A service account (e.g., svc-azure-automation) adds a new application registration to support an automation workflow (e.g., Azure Automation Runbook).
Filter/Exclusion: Exclude activities performed by service accounts associated with automation (e.g., caller = 'svc-azure-automation').
Scenario: A user adds a new service principal for a legitimate internal application (e.g., internal-app-portal) during a security incident response.
Filter/Exclusion: Exclude activities where the application_name matches known internal applications (e.g., application_name = 'internal-app-portal').