Query Details

HUNT 18 Cloud App Events SP Access Profile 30d

Query

// 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

Explanation

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:

  1. 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.

  2. Data Sources: It uses two main data tables:

    • CloudAppEvents: Logs of application activities.
    • AADServicePrincipalSignInLogs: Logs of service principal sign-ins.
  3. 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.

  4. Joining Profiles: The query joins the application activity profile with the sign-in profile based on the application ID.

  5. 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.

  6. 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.

Details

David Alonso profile picture

David Alonso

Released: April 21, 2026

Tables

CloudAppEventsAADServicePrincipalSignInLogs

Keywords

CloudAppEventsAADServicePrincipalSignInLogsApplicationIdApplicationIPAddressCountryCodeObjectNameAccountUpnActionTypeTimeGeneratedServicePrincipalNameServicePrincipalIdClientCredentialTypeResourceDisplayNameLocation

Operators

letdynamicinvokewhereisnotemptytostringextendsummarizecountcountifdcountmake_setanyminmaxbyjoinkindleftouteron$left$right==extendroundtodoubleiff>/caseandprojectorder bydesc

Actions