Adversaries may exploit approved access packages to maintain persistent access within an organization by leveraging legitimate administrative privileges. SOC teams should proactively hunt for unusual approval patterns or access justifications in Azure Sentinel to identify potential abuse of governance tools.
KQL Query
//Approved requests for all Access Packages with timestamps and justifications
AuditLogs
| where OperationName == "User requests access package assignment"
| mv-apply AdditionalDetails on (
where AdditionalDetails.key == "Justification"
| extend RequestorJustification = tostring(AdditionalDetails.value)
)
| mv-apply TR=TargetResources on (
where TR.type == "AccessPackageAssignmentRequest"
| extend RequestID = tostring(TR.id)
)
| mv-apply TR=TargetResources on (
where TR.type == "AccessPackage"
| extend AccessPackageName = tostring(TR.displayName)
)
| extend RequestedBy = tostring(InitiatedBy.user.userPrincipalName)
| distinct TimeRequested = TimeGenerated, AccessPackageName, RequestedBy, RequestorJustification, CorrelationId, RequestID
| join kind=inner (
AuditLogs
| where OperationName == "Approve access package assignment request"
| extend ApprovedBy = InitiatedBy.user.userPrincipalName
| project ApprovedTime = TimeGenerated, ApprovedBy, CorrelationId
) on CorrelationId
| join kind=leftouter (
AuditLogs
| where OperationName == "Request approved"
| mv-apply AdditionalDetails on (
where AdditionalDetails.key == "Justification"
| extend ApproverJustification = tostring(AdditionalDetails.value)
)
| mv-apply TargetResources on (
where TargetResources.type == "Other"
| extend RequestID = tostring(TargetResources.id)
)
| project ApproverJustification, RequestID
) on RequestID
| project TimeRequested, AccessPackageName, RequestedBy, RequestorJustification, ApprovedTime, ApprovedBy, ApproverJustification, CorrelationId, RequestID
| order by TimeRequested asc
id: aff7eb5f-3359-48ab-b73b-6c466d3806dc
name: Approved Access Packages Details
description: |
This query shows details about all approved Entra ID Governance Access Packages assignments. The results include the time the request was created and approved along with the justification text provided by the requestor and the approver.
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- AuditLogs
tactics:
- DefenseEvasion
- Persistence
relevantTechniques:
- T1556
query: |
//Approved requests for all Access Packages with timestamps and justifications
AuditLogs
| where OperationName == "User requests access package assignment"
| mv-apply AdditionalDetails on (
where AdditionalDetails.key == "Justification"
| extend RequestorJustification = tostring(AdditionalDetails.value)
)
| mv-apply TR=TargetResources on (
where TR.type == "AccessPackageAssignmentRequest"
| extend RequestID = tostring(TR.id)
)
| mv-apply TR=TargetResources on (
where TR.type == "AccessPackage"
| extend AccessPackageName = tostring(TR.displayName)
)
| extend RequestedBy = tostring(InitiatedBy.user.userPrincipalName)
| distinct TimeRequested = TimeGenerated, AccessPackageName, RequestedBy, RequestorJustification, CorrelationId, RequestID
| join kind=inner (
AuditLogs
| where OperationName == "Approve access package assignment request"
| extend ApprovedBy = InitiatedBy.user.userPrincipalName
| project ApprovedTime = TimeGenerated, ApprovedBy, CorrelationId
) on CorrelationId
| join kind=leftouter (
AuditLogs
| where OperationName == "Request approved"
| mv-apply AdditionalDetails on (
where AdditionalDetails.key == "Justification"
| extend ApproverJustification = tostring(AdditionalDetails.value)
)
| mv-apply TargetResources on (
where TargetResources.type == "Other"
| extend RequestID = tostring(TargetResources.id)
)
| project ApproverJustification, RequestID
) on RequestID
| project TimeRequested, AccessPackageName, RequestedBy, RequestorJustification, ApprovedTime, ApprovedBy, ApproverJustification, CorrelationId, RequestID
| order by TimeRequested asc
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: RequestedBy
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: ApprovedBy
| Sentinel Table | Notes |
|---|---|
AuditLogs | Ensure this data connector is enabled |
Scenario: Scheduled Access Package Sync Job
Description: A scheduled job runs to synchronize access package assignments with Azure AD, which may trigger the rule as it creates and approves assignments.
Filter/Exclusion: Filter by created_by field to exclude system accounts or service principals associated with the sync job (e.g., [email protected]).
Scenario: Admin Task: Review and Approve Access Requests
Description: An admin manually reviews and approves access requests through the Azure AD admin center, which is a legitimate governance activity.
Filter/Exclusion: Filter by requester_id or approver_id to exclude known admin accounts (e.g., [email protected]).
Scenario: Access Package Assignment via PowerShell
Description: An admin uses Azure AD PowerShell to assign access packages to users, which may be logged as an approval event.
Filter/Exclusion: Filter by operation_type or tool_used to exclude PowerShell scripts (e.g., Microsoft.Azure.ActiveDirectory.SignInLogs or Microsoft.Azure.ActiveDirectory.AccessReviews).
Scenario: Access Package Assignment via API
Description: An automated process or third-party tool uses the Microsoft Graph API to assign access packages, which may be flagged as an approval event.
Filter/Exclusion: Filter by caller_id or client_app_id to exclude known API clients or service accounts (e.g., Microsoft Graph API or [email protected]).
Scenario: Access Package Assignment via Azure AD Identity Governance Portal
Description: A user or admin assigns access packages through the Azure AD Identity Governance portal, which may be logged as an approval event.
Filter/Exclusion: Filter by user_principal_name or user_type to