Query Details

Audit User Marked As Compromised By Admin Or App

Query

AuditLogs
| where OperationName == "ConfirmAccountCompromised"
| extend IntiatedBy = parse_json(tostring(InitiatedBy.user)).userPrincipalName //Person who did the confirm Compromise. It may be an app
| extend ServicePrincipalId = tostring(parse_json(tostring(InitiatedBy.app)).appId)
| extend UserId = tostring(TargetResources[0].id) //Extract Target User Id
| join kind=leftouter (SigninLogs|where UserPrincipalName contains "@" |summarize by UserPrincipalName,UserId) on UserId //If UPN is missing use sign-in logs to gather it
| join kind=leftouter (AADServicePrincipalSignInLogs| summarize by ServicePrincipalId, ServicePrincipalName) on ServicePrincipalId //If an SP marked compromised use this to gather app display name
| join kind=leftouter (AADManagedIdentitySignInLogs| summarize by ServicePrincipalId, ServicePrincipalName) on ServicePrincipalId //IF MI marked compromised
| project-away UserId1, UserId, Type, AADOperationType, Level, DurationMs, ResultSignature, OperationVersion, Resource, ResourceGroup

Explanation

This query is designed to analyze audit logs to identify and provide details about accounts that have been confirmed as compromised. Here's a simplified breakdown of what the query does:

  1. Filter Audit Logs: It starts by filtering the AuditLogs table to only include entries where the operation name is "ConfirmAccountCompromised". This means it focuses on events where an account has been marked as compromised.

  2. Extract Initiator Information: It extracts the user principal name of the entity (person or application) that confirmed the account compromise. This is stored in a new column called InitiatedBy.

  3. Extract Service Principal ID: It extracts the application ID if the initiator is an application, storing it in a column named ServicePrincipalId.

  4. Extract Target User ID: It extracts the ID of the user account that was marked as compromised, storing it in a column named UserId.

  5. Join with Sign-in Logs: It performs a left outer join with the SigninLogs table to find the user principal name (UPN) for the compromised account if it's missing, using the UserId as the key for joining.

  6. Join with Service Principal Sign-in Logs: It performs another left outer join with the AADServicePrincipalSignInLogs table to get the display name of the application if a service principal (SP) was marked as compromised, using the ServicePrincipalId as the key.

  7. Join with Managed Identity Sign-in Logs: It performs a similar join with the AADManagedIdentitySignInLogs table to get the display name if a managed identity (MI) was marked as compromised.

  8. Project Away Unnecessary Columns: Finally, it removes unnecessary columns from the result to clean up the output, keeping only the relevant information.

In summary, this query identifies compromised accounts, provides details about who confirmed the compromise, and enriches the data with additional information from sign-in logs to give a clearer picture of the compromised accounts and any associated applications.

Details

Jay Kerai profile picture

Jay Kerai

Released: May 13, 2025

Tables

AuditLogsSigninLogsAADServicePrincipalSignInLogsAADManagedIdentitySignInLogs

Keywords

AuditLogsSigninLogsAADServicePrincipalSignInLogsAADManagedIdentitySignInLogsOperationNameInitiatedByUserPrincipalNameServicePrincipalIdUserIdServicePrincipalName

Operators

AuditLogs|where=="ConfirmAccountCompromised"|extend=parse_jsontostring.//|extend=tostringparse_jsontostring.|extend=tostring[0].//|joinkind=leftouter(SigninLogs|wherecontains"@"|summarizeby)on//|joinkind=leftouter(AADServicePrincipalSignInLogs|summarizeby)on//|joinkind=leftouter(AADManagedIdentitySignInLogs|summarizeby)on//|project-away.

Actions