Query Details
// Hunt : Workload Identity - CloudAppEvents SP/OAuth App Access Anomaly Profile (30d)
// Purpose : Profiles all service principals and OAuth applications visible in CloudAppEvents
// over 30 days, ranked by volume, object diversity, and unique user spread. Joins
// with AADServicePrincipalSignInLogs to add authentication metadata (IPs, cred types,
// countries). Identifies applications performing broad collection, mass enumeration,
// or cross-user access patterns that exceed normal operational behaviour. Use to
// baseline legitimate automation and surface abnormal data-access footprints.
// Tables : CloudAppEvents, AADServicePrincipalSignInLogs
// Period : P30D
// Tactics : Collection, Discovery, Exfiltration
// MITRE : T1530, T1213, T1087.004
//==========================================================================================
let LookbackDays = 30d;
let HighVolumeActions = dynamic([
"FileDownloaded", "FileRead", "FilePreviewed", "FileCopied", "FileSyncUploadedFull",
"FolderListed", "SearchQueried",
"MailItemsAccessed", "MessageRead", "Send",
"TeamsMessageRead"
]);
let LowVolumeActions = dynamic([
"FileCreated", "FileModified", "FileUploaded",
"MeetingParticipant", "UserLoggedIn"
]);
// Per-app profile from CloudAppEvents
let AppProfile = (CloudAppEvents | invoke ExcludeAllowlistedIPs())
| where TimeGenerated > ago(LookbackDays)
| where isnotempty(tostring(ApplicationId))
| extend AppIdStr = tostring(ApplicationId)
| extend AppName = tostring(Application)
| extend IPAddr = tostring(IPAddress)
| extend Country = tostring(CountryCode)
| extend ObjName = tostring(ObjectName)
| extend ActorUpn = tostring(AccountUpn)
| summarize
TotalActions = count(),
HighVolumeActions = countif(ActionType in~ (HighVolumeActions)),
LowVolumeActions = countif(ActionType in~ (LowVolumeActions)),
UniqueObjects = dcount(ObjName),
UniqueUsers = dcount(ActorUpn),
UniqueIPs = dcount(IPAddr),
ActionTypes = make_set(ActionType, 15),
TopObjects = make_set(ObjName, 10),
Countries = make_set(Country, 5),
AppName = any(AppName),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated)
by AppIdStr;
// SP sign-in metadata from AADServicePrincipalSignInLogs
let SPSigninProfile = (AADServicePrincipalSignInLogs | invoke ExcludeAllowlistedIPs())
| where TimeGenerated > ago(LookbackDays)
| where ResultType == "0"
| summarize
SigninCount = count(),
SigninIPs = dcount(IPAddress),
SigninIPList = make_set(IPAddress, 10),
CredTypes = make_set(ClientCredentialType, 5),
Resources = make_set(ResourceDisplayName, 10),
SigninCountries = make_set(Location, 5),
FailedSignins = countif(ResultType != "0"),
FirstSignin = min(TimeGenerated),
LastSignin = max(TimeGenerated)
by AppId, ServicePrincipalName, ServicePrincipalId;
// Join profiles
AppProfile
| join kind=leftouter SPSigninProfile on $left.AppIdStr == $right.AppId
| extend HighVolumeRatio = round(todouble(HighVolumeActions) / iff(TotalActions > 0, todouble(TotalActions), 1.0), 2)
| extend UserSpreadRatio = round(todouble(UniqueUsers) / iff(UniqueObjects > 0, todouble(UniqueObjects), 1.0), 2)
| extend AnomalyScore = case(
HighVolumeRatio > 0.8 and UniqueUsers > 20, "Critical",
HighVolumeRatio > 0.6 and UniqueObjects > 50, "High",
HighVolumeRatio > 0.4, "Medium",
"Low")
| project
AppIdStr, AppName, ServicePrincipalId, ServicePrincipalName,
TotalActions, HighVolumeActions, HighVolumeRatio,
UniqueObjects, UniqueUsers, UniqueIPs, UserSpreadRatio,
ActionTypes, TopObjects, Countries,
SigninCount, SigninIPs, SigninIPList, CredTypes, Resources, SigninCountries,
AnomalyScore, FirstSeen, LastSeen
| order by TotalActions desc
This query is designed to analyze and profile service principals and OAuth applications based on their activities over the past 30 days. Here's a simplified breakdown of what it does:
Purpose: The query aims to identify unusual access patterns by applications, such as broad data collection or mass enumeration, which might indicate abnormal behavior. It helps establish a baseline for normal operations and highlights potential security concerns.
Data Sources: It uses two main data tables:
Process:
App Profile Creation: It first profiles each application by summarizing its activities from the CloudAppEvents table. It counts the total actions, distinguishes between high and low volume actions, and tracks unique objects, users, and IPs involved. It also records the types of actions performed and the countries from which these actions originated.
Sign-in Profile Creation: It then profiles sign-in activities from the AADServicePrincipalSignInLogs table, counting successful sign-ins, unique IPs, credential types used, and the resources accessed.
Joining Profiles: The query joins the application activity profile with the sign-in profile based on the application ID.
Anomaly Detection: It calculates ratios to assess the volume of high-frequency actions and the spread of user interactions. Based on these metrics, it assigns an anomaly score (Critical, High, Medium, Low) to indicate the level of unusual activity.
Output: The final output includes detailed information about each application, such as its ID, name, total actions, unique users and objects, sign-in details, and the calculated anomaly score. The results are sorted by the total number of actions to prioritize the most active applications.
This query is useful for security analysts to monitor and detect potential security threats related to application access and usage in a cloud environment.

David Alonso
Released: April 21, 2026
Tables
Keywords
Operators