Query Details
// Owner added to high privileged application to unprivileged or lower privileged user
let SensitiveMsGraphPermissions = externaldata(EAMTierLevel: string, Category: string, APIPermission: string)["https://raw.githubusercontent.com/Cloud-Architekt/AzurePrivilegedIAM/main/Classification/Classification_MsGraphPermissions.json"] with(format='multijson');
let SensitiveAadDirectoryRoles = externaldata(EAMTierLevel: string, Category: string, id: string)["https://raw.githubusercontent.com/Cloud-Architekt/AzurePrivilegedIAM/main/Classification/Classification_AadDirectoryRoles.json"] with(format='multijson');
AuditLogs
| where TimeGenerated >ago(14d)
| where OperationName in ("Add owner to application","Add owner to service principal")
| extend InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))
| extend InitiatingUserOrAppId = iff(isnotempty(InitiatedBy.user.id),tostring(InitiatedBy.user.id), tostring(InitiatedBy.app.id))
| extend InitiatingIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), tostring(InitiatedBy.user.ipAddress), tostring(InitiatedBy.app.ipAddress))
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties | where TargetResources_modifiedProperties.displayName == "Application.ObjectID" | extend ApplicationObjectID = replace_string(tostring(TargetResources_modifiedProperties.newValue),'"','')
| mv-expand TargetResources.modifiedProperties | where TargetResources_modifiedProperties.displayName == "Application.DisplayName" | extend ApplicationDisplayName = replace_string(tostring(TargetResources_modifiedProperties.newValue),'"','')
| mv-expand TargetResources.modifiedProperties | where TargetResources_modifiedProperties.displayName == "Application.AppId" | extend ApplicationAppId = replace_string(tostring(TargetResources_modifiedProperties.newValue),'"','')
| extend AddedOwnerId = tostring(TargetResources.id)
| join kind=inner(
AADServicePrincipals_CL
| mv-expand parse_json(APP_s), parse_json(SPAppRoleAssignments_s)
| mv-expand SPAppRoleAssignments_s.AppRolePermissionSensitivity, SPAppRoleAssignments_s.AppRolePermission, parse_json(SPAADRoleAssignments_s)
| mv-expand SPAADRoleAssignments_s.roleDefinitionName, SPAADRoleAssignments_s.roleDefinitionId
| extend AppRolePermission = tostring(SPAppRoleAssignments_s_AppRolePermission)
| extend AadDirectoryRoleId = tostring(SPAADRoleAssignments_s_roleDefinitionId)
| extend AadDirectoryRoleName = tostring(SPAADRoleAssignments_s_roleDefinitionName)
// Option A: Filter on classification from AadSpInsights
//| where SPAADRoleAssignments_s_roleDefinitionId == true or SPAppRoleAssignments_s_AppRolePermissionSensitivity == "critical"
// Option B: Filter on enrichment from external classification schema
| join kind=leftouter(
SensitiveMsGraphPermissions | project AppRolePermissionTierLevel = EAMTierLevel, AppRolePermissionCategory = Category, APIPermission
) on $left.AppRolePermission == $right.APIPermission
| join kind=leftouter(
SensitiveAadDirectoryRoles | project AadDirectoryRoleTierLevel = EAMTierLevel, AadDirectoryRoleTierCategory = Category, id
) on $left.AadDirectoryRoleId == $right.id
| extend AppClassification = iif((AppRolePermissionTierLevel == "ControlPlane" or AadDirectoryRoleTierLevel == "ControlPlane"), "ControlPlane", "ManagementDataPlane")
| project APPObjectId = tostring(parse_json(APP_s).APPObjectId), APPDisplayName = tostring(parse_json(APP_s).APPDisplayName), ObjectId_g, ObjectType_s, AppRolePermission, AadDirectoryRoleName, AppClassification, AppRolePermissionCategory, AadDirectoryRoleTierCategory, tostring(parse_json(APP_s).APPSignInAudience)
| distinct APPObjectId, APPDisplayName, ObjectId_g, ObjectType_s, AppClassification
) on $left.ApplicationObjectID == $right.APPObjectId
| join kind=inner (
PrivilegedEAM_CL
| where TimeGenerated > ago(14d)
| summarize arg_max(TimeGenerated, *) by ObjectId
| extend AddedOwnerClassification = iff(isnotempty(parse_json(Classification)), parse_json(Classification), "UserAccess")
| project ObjectId, ObjectAdminTierLevel, ObjectAdminTierLevelName, AddedOwnerClassification, AddedOwnerUserPrincipalName = ObjectUserPrincipalName, AddedOwnerDisplayName = ObjectDisplayName
) on $left.AddedOwnerId == $right.ObjectId
| where parse_json(tostring(parse_json(AddedOwnerClassification))) !contains AppClassification
| project-reorder OperationName, ApplicationObjectID, ApplicationDisplayName, ApplicationAppId, AppClassification, InitiatingUserOrApp, InitiatingUserOrAppId, InitiatingIpAddress, AddedOwnerId, AddedOwnerClassification, AddedOwnerUserPrincipalName, AddedOwnerDisplayName
This query is looking for instances where an owner is added to a high privileged application and assigned to an unprivileged or lower privileged user. It retrieves data from external sources to classify the permissions and roles involved. It then joins this data with audit logs and privileged access logs to identify the application, user, and their respective classifications. The query filters out instances where the application's classification is not contained in the added owner's classification. The final result includes information about the operation, application, user or app initiating the operation, IP address, added owner, and their respective classifications.

Thomas Naunheim
Released: September 16, 2023
Tables
Keywords
Operators