Query Details

RULE 08 SP Credential Added Privileged Role

Query

// Rule    : Azure - Service Principal Credential Added Then Privileged Role Assigned (Same Initiator, 60 min)
// Severity: High
// Tactics : Persistence, PrivilegeEscalation
// MITRE   : T1098
// Freq    : PT1H   Period: PT2H
//==========================================================================================

let CredentialAdditions = AuditLogs
    | where TimeGenerated > ago(2h)
    | where OperationName has_any ("Add service principal credentials", "Update application – Certificates and secrets management")
    | where Result =~ "success"
    | extend SPName = tostring(TargetResources[0].displayName)
    | extend Initiator = coalesce(tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
    | where isnotempty(Initiator)
    | project CredTime = TimeGenerated, Initiator, SPName, CorrelationId;
let PrivilegedRoleAssignments = AuditLogs
    | where TimeGenerated > ago(2h)
    | where OperationName has_any ("Add member to role", "Add app role assignment to service principal", "Add eligible member to role")
    | where Result =~ "success"
    | extend RoleName = tostring(TargetResources[0].displayName)
    | where RoleName has_any ("Owner", "Contributor", "User Access Administrator", "Global Administrator", "Privileged Role Administrator", "Security Administrator", "Application Administrator")
    | extend Initiator = coalesce(tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
    | where isnotempty(Initiator)
    | project RoleTime = TimeGenerated, Initiator, RoleName, CorrelationId;
CredentialAdditions
| join kind=inner PrivilegedRoleAssignments on Initiator
| where RoleTime >= CredTime and RoleTime <= CredTime + 1h
| summarize
    EventCount = count(),
    AssignedRoles = make_set(RoleName, 5),
    AffectedSPs = make_set(SPName, 5),
    CorrelationIds = make_set(CorrelationId, 5),
    FirstEvent = min(CredTime),
    LastEvent = max(RoleTime)
    by Initiator
| extend
    AccountName = tostring(split(Initiator, "@")[0]),
    AccountUPNSuffix = tostring(split(Initiator, "@")[1])

Explanation

This query is designed to detect potentially suspicious activity in Azure by identifying instances where a service principal's credentials are added or updated, followed by the assignment of a privileged role by the same initiator within a 60-minute window. Here's a simplified breakdown:

  1. Credential Additions: The query first looks at audit logs from the past 2 hours to find successful operations where service principal credentials were added or updated. It captures details like the time of the event, the initiator (who performed the action), the service principal's name, and a correlation ID for tracking.

  2. Privileged Role Assignments: Next, it examines the same audit logs for successful operations where a privileged role (like Owner, Contributor, or Administrator roles) was assigned. It again captures details such as the time of the event, the initiator, the role name, and a correlation ID.

  3. Correlation: The query then joins these two sets of data on the initiator, looking for cases where the role assignment happened within 60 minutes after the credential addition.

  4. Summary: For each initiator, it summarizes the number of such events, the roles assigned, the affected service principals, and the correlation IDs. It also notes the time of the first and last events in this sequence.

  5. Output: Finally, it extracts and displays the account name and domain part of the initiator's email address.

This query helps in identifying potential security risks by flagging scenarios where someone might be trying to escalate privileges shortly after modifying service principal credentials, which could indicate malicious activity.

Details

David Alonso profile picture

David Alonso

Released: March 12, 2026

Tables

AuditLogs

Keywords

AzureAuditLogsServicePrincipalCredentialsRoleAssignmentsAdministratorUser

Operators

let|wherehas_any=~extendtostringcoalesceisnotemptyprojectjoinkind=inner>=<=+summarizecountmake_setminmaxbysplit

Actions