← Back to SOC feed Coverage →

New service principal granted admin consent within one hour of creation

kql MEDIUM Azure-Sentinel
T1528T1098.003
AuditLogs
huntingmicrosoftofficialpersistence
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-05-27T23:00:00Z · Confidence: medium

Hunt Hypothesis

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

Analytic Rule Definition

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

Required Data Sources

Sentinel TableNotes
AuditLogsEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/AuditLogs/NewServicePrincipalGrantedAdminConsent.yaml