A user with recently granted privileged roles may have added a service principal credential to exfiltrate data or establish persistence. SOC teams should proactively hunt for this behavior as it indicates potential post-compromise activity leveraging elevated privileges.
KQL Query
let timeframe = 1d;
let roleWindow = 24h;
let lookback = 7d;
let PrivilegedRoles = dynamic([
"Application Administrator",
"Cloud Application Administrator",
"Global Administrator",
"Privileged Role Administrator"
]);
let RecentRoleGrants =
AuditLogs
| where TimeGenerated >= ago(timeframe + lookback) and TimeGenerated < ago(0h)
| where OperationName in~ ("Add member to role.", "Add member to role")
| where Result =~ "success"
| extend NewRoleUser = tolower(tostring(TargetResources[0].userPrincipalName))
| extend RoleName = tostring(TargetResources[1].displayName)
| where RoleName in~ (PrivilegedRoles)
| where isnotempty(NewRoleUser)
| project NewRoleUser, RoleName, RoleGrantedTime = TimeGenerated;
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ (
"Add service principal credentials",
"Update application - Certificates and secrets management"
)
| where Result =~ "success"
| extend ActorUpn = tolower(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 TargetSpName = tostring(TargetResources[0].displayName)
| extend TargetSpId = tostring(TargetResources[0].id)
| where isnotempty(ActorUpn)
| join kind=inner RecentRoleGrants on $left.ActorUpn == $right.NewRoleUser
| where TimeGenerated >= RoleGrantedTime and TimeGenerated <= RoleGrantedTime + roleWindow
| extend AccountName = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[0]), ActorUpn)
| extend AccountUPNSuffix = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[1]), "")
| project
TimeGenerated,
ActorUpn,
AccountName,
AccountUPNSuffix,
RoleName,
RoleGrantedTime,
TargetSpName,
TargetSpId,
ActorIp,
CorrelationId
| sort by TimeGenerated desc
id: 661d71d1-98a4-464f-bb6b-fc3c39499b3f
name: Service principal credential added by user granted privileged role in last 24 hours
description: Identifies service principal credential additions by users who received Application Administrator or Global Administrator roles within the preceding 24 hours, consistent with immediate post-compromise privilege abuse.
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- Persistence
relevantTechniques:
- T1098.001
query: |
let timeframe = 1d;
let roleWindow = 24h;
let lookback = 7d;
let PrivilegedRoles = dynamic([
"Application Administrator",
"Cloud Application Administrator",
"Global Administrator",
"Privileged Role Administrator"
]);
let RecentRoleGrants =
AuditLogs
| where TimeGenerated >= ago(timeframe + lookback) and TimeGenerated < ago(0h)
| where OperationName in~ ("Add member to role.", "Add member to role")
| where Result =~ "success"
| extend NewRoleUser = tolower(tostring(TargetResources[0].userPrincipalName))
| extend RoleName = tostring(TargetResources[1].displayName)
| where RoleName in~ (PrivilegedRoles)
| where isnotempty(NewRoleUser)
| project NewRoleUser, RoleName, RoleGrantedTime = TimeGenerated;
AuditLogs
| where TimeGenerated >= ago(timeframe)
| where OperationName in~ (
"Add service principal credentials",
"Update application - Certificates and secrets management"
)
| where Result =~ "success"
| extend ActorUpn = tolower(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 TargetSpName = tostring(TargetResources[0].displayName)
| extend TargetSpId = tostring(TargetResources[0].id)
| where isnotempty(ActorUpn)
| join kind=inner RecentRoleGrants on $left.ActorUpn == $right.NewRoleUser
| where TimeGenerated >= RoleGrantedTime and TimeGenerated <= RoleGrantedTime + roleWindow
| extend AccountName = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[0]), ActorUpn)
| extend AccountUPNSuffix = iff(ActorUpn has "@", tostring(split(ActorUpn, "@")[1]), "")
| project
TimeGenerated,
ActorUpn,
AccountName,
AccountUPNSuffix,
RoleName,
RoleGrantedTime,
TargetSpName,
TargetSpId,
ActorIp,
CorrelationId
| sort by TimeGenerated desc
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ActorUpn
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier:
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
Scenario: A user adds a service principal credential as part of a scheduled job to automate report generation using Power Automate or Azure Logic Apps.
Filter/Exclusion: Exclude service principals created by Power Automate or Logic Apps workflows, or filter by created by user field to exclude known automation tools.
Scenario: A system administrator provisions a service principal for Microsoft Intune or Azure AD Connect during routine maintenance, which requires granting Application Administrator role temporarily.
Filter/Exclusion: Exclude service principals associated with Intune, Azure AD Connect, or Microsoft Endpoint Manager tools, or use a tool-specific identifier in the detection logic.
Scenario: A user grants Global Administrator role to a service principal as part of a Microsoft 365 backup or compliance tool (e.g., Microsoft Purview or Azure Backup) to enable full access for data collection.
Filter/Exclusion: Exclude service principals linked to Microsoft Purview, Azure Backup, or Microsoft 365 Compliance Center tools, or check for specific application IDs used by these services.
Scenario: A user adds a service principal credential to support Azure DevOps pipeline integration, which requires Application Administrator privileges during setup.
Filter/Exclusion: Exclude service principals associated with Azure DevOps or GitHub Actions, or filter by application ID or resource group related to CI/CD pipelines.
Scenario: A user grants Global Administrator role to a service principal for Microsoft Teams or Microsoft Exchange Online integration, which is part of a standard deployment process.
Filter/Exclusion: Exclude service principals used by Microsoft Teams, Exchange Online, or Microsoft Graph APIs, or use application-specific identifiers