Adversaries may create a service principal and immediately grant it admin consent to establish persistent access within the tenant. SOC teams should proactively hunt for this behavior in Azure Sentinel to detect potential post-compromise persistence and privilege escalation tactics.
KQL Query
let timeframe = 1d;
let correlationWindow = 1h;
let NewSP =
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName =~ "Add service principal"
| where Result =~ "success"
| extend CreatingActorUpn = tostring(InitiatedBy.user.userPrincipalName)
| extend CreatingActorApp = tostring(InitiatedBy.app.displayName)
| mv-expand TargetResource = TargetResources
| where tostring(TargetResource.type) =~ "ServicePrincipal"
| project
SpCreatedTime = TimeGenerated,
SpId = tostring(TargetResource.id),
SpName = tostring(TargetResource.displayName),
CreatingActorUpn,
CreatingActorApp;
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ (
"Add app role assignment to service principal",
"Consent to application"
)
| where Result =~ "success"
| extend ConsentActorUpn = tostring(InitiatedBy.user.userPrincipalName)
| extend ConsentActorApp = tostring(InitiatedBy.app.displayName)
| extend ConsentActorIp = iff(
isnotempty(tostring(InitiatedBy.user.ipAddress)),
tostring(InitiatedBy.user.ipAddress),
tostring(InitiatedBy.app.ipAddress))
| mv-expand TargetResource = TargetResources
| where tostring(TargetResource.type) =~ "ServicePrincipal"
| extend ConsentTargetId = tostring(TargetResource.id)
| join kind=inner NewSP on $left.ConsentTargetId == $right.SpId
| where TimeGenerated between (SpCreatedTime .. (SpCreatedTime + correlationWindow))
| extend CreatingActor = iff(isnotempty(CreatingActorUpn), CreatingActorUpn, CreatingActorApp)
| extend ConsentActor = iff(isnotempty(ConsentActorUpn), ConsentActorUpn, ConsentActorApp)
| extend AccountName = iff(ConsentActor has "@",
tostring(split(ConsentActor, "@")[0]), ConsentActor)
| extend AccountUPNSuffix = iff(ConsentActor has "@",
tostring(split(ConsentActor, "@")[1]), "")
| project
TimeGenerated,
SpName,
SpId,
OperationName,
CreatingActor,
ConsentActor,
AccountName,
AccountUPNSuffix,
ConsentActorIp,
SpCreatedTime,
CorrelationId
| sort by TimeGenerated desc
id: dc9c0f2a-68f4-4415-aa7e-81d87149c222
name: New service principal granted admin consent within one hour of creation
description: |
Identifies service principals that received an app role assignment or admin consent
within one hour of being registered in the tenant. Register-then-consent is a
documented persistence pattern after privileged account compromise.
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- Persistence
- PrivilegeEscalation
relevantTechniques:
- T1528
- T1098.003
query: |
let timeframe = 1d;
let correlationWindow = 1h;
let NewSP =
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName =~ "Add service principal"
| where Result =~ "success"
| extend CreatingActorUpn = tostring(InitiatedBy.user.userPrincipalName)
| extend CreatingActorApp = tostring(InitiatedBy.app.displayName)
| mv-expand TargetResource = TargetResources
| where tostring(TargetResource.type) =~ "ServicePrincipal"
| project
SpCreatedTime = TimeGenerated,
SpId = tostring(TargetResource.id),
SpName = tostring(TargetResource.displayName),
CreatingActorUpn,
CreatingActorApp;
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ (
"Add app role assignment to service principal",
"Consent to application"
)
| where Result =~ "success"
| extend ConsentActorUpn = tostring(InitiatedBy.user.userPrincipalName)
| extend ConsentActorApp = tostring(InitiatedBy.app.displayName)
| extend ConsentActorIp = iff(
isnotempty(tostring(InitiatedBy.user.ipAddress)),
tostring(InitiatedBy.user.ipAddress),
tostring(InitiatedBy.app.ipAddress))
| mv-expand TargetResource = TargetResources
| where tostring(TargetResource.type) =~ "ServicePrincipal"
| extend ConsentTargetId = tostring(TargetResource.id)
| join kind=inner NewSP on $left.ConsentTargetId == $right.SpId
| where TimeGenerated between (SpCreatedTime .. (SpCreatedTime + correlationWindow))
| extend CreatingActor = iff(isnotempty(CreatingActorUpn), CreatingActorUpn, CreatingActorApp)
| extend ConsentActor = iff(isnotempty(ConsentActorUpn), ConsentActorUpn, ConsentActorApp)
| extend AccountName = iff(ConsentActor has "@",
tostring(split(ConsentActor, "@")[0]), ConsentActor)
| extend AccountUPNSuffix = iff(ConsentActor has "@",
tostring(split(ConsentActor, "@")[1]), "")
| project
TimeGenerated,
SpName,
SpId,
OperationName,
CreatingActor,
ConsentActor,
AccountName,
AccountUPNSuffix,
ConsentActorIp,
SpCreatedTime,
CorrelationId
| sort by TimeGenerated desc
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ConsentActor
- identifier: Name
columnName: AccountNam
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
Scenario: A system administrator creates a new service principal for a third-party monitoring tool and immediately grants it admin consent to enable real-time monitoring.
Filter/Exclusion: Exclude service principals created by users with the Global Administrator role and associated with known monitoring or compliance tools (e.g., Azure Security Center, Microsoft Sentinel).
Scenario: A scheduled job runs every hour to refresh access tokens for a service principal, which triggers an admin consent event due to token refresh logic.
Filter/Exclusion: Exclude events where the service principal is associated with a known token refresh or identity management tool (e.g., Azure AD Connect, Azure DevOps pipelines).
Scenario: An IT support team creates a temporary service principal for a user to access internal resources and grants admin consent as part of a quick setup process.
Filter/Exclusion: Exclude service principals created by users in the IT Support or Help Desk security group and associated with temporary access or user provisioning tools (e.g., Azure AD Privileged Identity Management).
Scenario: A DevOps pipeline deploys a new application and automatically registers a service principal, then grants it admin consent to configure Azure resources during deployment.
Filter/Exclusion: Exclude service principals created by CI/CD pipelines (e.g., Azure DevOps, GitHub Actions) and associated with infrastructure-as-code (IaC) tools (e.g., Terraform, ARM templates).
Scenario: A compliance audit tool is configured to register a service principal and grant admin consent to access audit logs and reports.
Filter/Exclusion: Exclude service principals created by compliance or audit tools (e.g., Microsoft Cloud App Security, Azure Information Protection) and associated with audit or reporting roles.