Query Details

Purview DLP Share Point Alert Info

Query

let alert_id = "<<<>>>";
let query_period = 1d;
let _FilePaths =
    AlertEvidence
    | where Timestamp > ago(query_period)
    | where EntityType == "File" and AlertId == strcat("dl", alert_id)
    | extend AdditionalFields = todynamic(AdditionalFields)
    | distinct SourceFilePath = strcat(AdditionalFields["Directory"], "/", AdditionalFields["Name"]), ObjectName = url_decode(tostring(AdditionalFields["Name"]))
;
CloudAppEvents
| where Timestamp > ago(query_period)
| where ActionType in ("AlertTriggered", "AlertUpdated") and tostring(RawEventData["AlertId"]) == alert_id
| summarize arg_max(Timestamp, *)
| extend
    AlertId = tostring(RawEventData["AlertId"]),
    Data = todynamic(tostring(RawEventData["Data"]))
| extend
    UserPrincipalName = tolower(tostring(Data["f3u"])),
    Severity = tostring(Data["sev"]),
    Workload = tostring(Data["wl"]),
    SensitiveInformationContentType = split(tostring(Data["sict"]), ";"),
    SensitiveInformationTypeMatchInfo = split(tostring(Data["sitmi"]), ";"),
    PolicyId = tostring(Data["dpid"]),
    PolicyName = tostring(Data["dpn"]),
    ManagementRuleId = tostring(Data["drid"]),
    //RuleId = tostring(Data["drid"]),
    RuleName = tostring(Data["drn"]),
    ProtectionActions = split(tostring(Data["dact"]), ", "),
    ObjectName = tostring(Data["von"]),
    DMC = split(tostring(Data["dmc"]), ","),
    FileOwnerName = tolower(tostring(Data["flmb"])), // It might not the same as f3u - UserPrincipalName
    FileOwnerDisplayName = tostring(Data["fo"]),
    USI = tostring(Data["usi"])
| where Workload == "SharePoint" and tostring(RawEventData["Category"]) == "DataLossPrevention"
| lookup kind=leftouter _FilePaths on ObjectName
| project
    Timestamp,
    Application,
    ActionType,
    AlertId,
    UserPrincipalName,
    Severity,
    Workload,
    SensitiveInformationContentType,
    SensitiveInformationTypeMatchInfo,
    PolicyId,
    PolicyName,
    ManagementRuleId,
    //RuleId,
    RuleName,
    DMC,
    ProtectionActions,
    ObjectName,
    SourceFilePath,
    FileOwnerName,
    FileOwnerDisplayName,
    USI,
    AlertTriggered_RawEventData = RawEventData,
    AlertTriggered_ReportId = ReportId
// | summarize arg_max(CreationTime, *) by AlertId, Timestamp, SourceFilePath, ObjectName, ManagementRuleId
| join hint.strategy=shuffle kind=leftouter (
    CloudAppEvents
    | where Timestamp > ago(query_period)
    | where ActionType in ("DLPRuleMatch", "DLPRuleUndo") and Application == "Microsoft SharePoint Online"
    | mv-expand PolicyDetail = iff(array_length(RawEventData["PolicyDetails"]) > 0, RawEventData["PolicyDetails"], dynamic([""]))
    | mv-expand Rule = iff(array_length(PolicyDetail["Rules"]) > 0, PolicyDetail["Rules"], dynamic([""]))
    | mv-apply SensitiveInformationItem = Rule["ConditionsMatched"]["SensitiveInformation"] on (
        summarize SensitiveInformation = make_list(bag_remove_keys(SensitiveInformationItem, dynamic(["SensitiveInformationDetailedClassificationAttributes"])))
    )
    | project
        CreationTime = todatetime(RawEventData["CreationTime"]),
        ManagementRuleId = tostring(Rule["ManagementRuleId"]),
        ObjectName,
        //IncidentId = tostring(RawEventData["IncidentId"]),
        SensitiveInformation,
        ActionType,
        Reason = tostring(RawEventData["ExceptionInfo"]["Reason"]),
        //SharePointMetaData = RawEventData["SharePointMetaData"],
        SiteCollectionUrl = tostring(RawEventData["SharePointMetaData"]["SiteCollectionUrl"]),
        //IsViewableByExternalUsers = tostring(RawEventData["SharePointMetaData"]["IsViewableByExternalUsers"]),
        //ItemLastSharedTime = todatetime(RawEventData["SharePointMetaData"]["ItemLastSharedTime"]),
        //SharedBy = todatetime(RawEventData["SharePointMetaData"]["SharedBy"]),
        DLPRuleMatch_RawEventData = RawEventData,
        DLPRuleMatch_ReportId = ReportId
    // The same file can be updated, and suffer several "DLPRuleMatch" and "DLPRuleUndo" actions
    | summarize
        EvaluationEventCount = count(),
        arg_min(CreationTime, SensitiveInformation, SiteCollectionUrl, DLPRuleMatch_RawEventData, DLPRuleMatch_ReportId),
        (LastEvaluationTime, LastActionType, LastReason, LastSensitiveInformation) = arg_max(CreationTime, ActionType, Reason, SensitiveInformation)
        by ObjectName, ManagementRuleId
    | extend
        LastEvaluationTime = iff(EvaluationEventCount == 1, datetime(null), LastEvaluationTime),
        LastActionType = iff(EvaluationEventCount == 1, "", LastActionType),
        LastReason = iff(EvaluationEventCount == 1, "", LastReason),
        LastSensitiveInformation = iff(EvaluationEventCount == 1, dynamic(null), LastSensitiveInformation)
    ) on $left.SourceFilePath == $right.ObjectName, ManagementRuleId //, CreationTime does not always match
| project-away ObjectName1, ManagementRuleId1
// Add info about the sharing targets
// Did not have any example to work with, where a DLP alert happened and the file had ActionType in ("AddedToGroup", "AddedToSecureLink", "AddedToSharingLink")
| as hint.materialized=true _Events
| join kind=leftouter (
    CloudAppEvents
    | where Timestamp > ago(query_period)
    | where Application == "Microsoft SharePoint Online" and ActionType in ("AddedToGroup", "AddedToSecureLink", "AddedToSharingLink") // "GroupAdded", "SecureLinkCreated", "SharingLinkCreated", "SharingSet", "SharingInheritanceBroken"
    | extend
        UserId = tostring(RawEventData["UserId"]),
        CreationTime = todatetime(RawEventData["CreationTime"]),
        SharingOperation = tostring(RawEventData["Operation"]),
        SiteUrl = tostring(RawEventData["SiteUrl"]),
        ListId = tostring(RawEventData["ListId"]),
        ListItemUniqueId = tostring(RawEventData["ListItemUniqueId"]),
        ObjectName = tostring(RawEventData["ObjectId"]),
        ItemType = tostring(RawEventData["ItemType"]),
        Permission = tostring(RawEventData["Permission"]),
        SharingLinkScope = tostring(RawEventData["SharingLinkScope"]),
        EventData = tostring(RawEventData["EventData"]),
        ModifiedProperties = tostring(RawEventData["ModifiedProperties"]),
        TargetUserOrGroupName = tostring(RawEventData["TargetUserOrGroupName"]),
        TargetUserOrGroupType = tostring(RawEventData["TargetUserOrGroupType"]),
        UniqueSharingId = tostring(RawEventData["UniqueSharingId"]),
        CorrelationId = tostring(RawEventData["CorrelationId"]),
        SharingEvent_RawEventData = RawEventData,
        SharingEvent_ReportId = ReportId
    | where tostring(toscalar(_Events | summarize make_set_if(url_decode(SourceFilePath), isnotempty(SourceFilePath)))) has ObjectName
    | summarize
        CreationTime = min(CreationTime),
        TargetUserOrGroupName = array_sort_asc(make_set_if(TargetUserOrGroupName, isnotempty(TargetUserOrGroupName))) // You could exclude targets from the same tenant
        by CorrelationId, SiteUrl, UserId, SharingOperation, UniqueSharingId, ObjectName, ItemType, Permission, SharingLinkScope, EventData, TargetUserOrGroupType
    | summarize
        CreationTime = min(CreationTime),
        TargetUserOrGroup = make_bag(bag_pack(TargetUserOrGroupType, TargetUserOrGroupName))
        by CorrelationId, SiteUrl, UserId, SharingOperation, UniqueSharingId, ObjectName, ItemType, Permission, SharingLinkScope, EventData
    | extend EmptyColumns = array_concat(
        iff(isnotempty(Permission), dynamic(null), dynamic(["Permission"])),
        iff(isnotempty(SharingLinkScope), dynamic(null), dynamic(["SharingLinkScope"])),
        iff(isnotempty(EventData), dynamic(null), dynamic(["EventData"]))
        )
    | summarize
        CreationTime = min(CreationTime),
        Operations = make_bag(bag_pack(SharingOperation, bag_remove_keys(bag_pack_columns(Permission, SharingLinkScope, EventData), EmptyColumns))),
        take_any(TargetUserOrGroup)
        by CorrelationId, SiteUrl, UserId, UniqueSharingId, ObjectName, ItemType, Auxiliar = tostring(TargetUserOrGroup)
    | summarize
        CreationTime = min(CreationTime),
        Objects = make_list(bag_pack_columns(ObjectName, ItemType, Operations)),
        take_any(TargetUserOrGroup)
        by CorrelationId, SiteUrl, UserId, Auxiliar
    | summarize
        SharingTime = min(CreationTime),
        SharingTargets = make_list(bag_pack_columns(TargetUserOrGroup, Objects))
        by CorrelationId, SiteUrl, UserId
    | sort by SharingTime asc
    | summarize
        SharingEvents = make_list(bag_pack_columns(SharingTime, CorrelationId, SharingTargets))
        by SiteUrl, UserId
    | project
        SiteCollectionUrl = SiteUrl,
        UserPrincipalName = UserId,
        SharingEvents
    ) on SiteCollectionUrl, UserPrincipalName
| project-away SiteCollectionUrl1, UserPrincipalName1
| project-reorder
    Timestamp,
    Application,
    ActionType,
    AlertId,
    UserPrincipalName,
    Severity,
    Workload,
    SensitiveInformationContentType,
    SensitiveInformationTypeMatchInfo,
    PolicyId,
    PolicyName,
    ManagementRuleId,
    //RuleId,
    RuleName,
    DMC,
    ProtectionActions,
    ObjectName,
    SourceFilePath,
    FileOwnerName,
    FileOwnerDisplayName,
    USI,
    AlertTriggered_RawEventData,
    AlertTriggered_ReportId,
    CreationTime,
    SensitiveInformation,
    EvaluationEventCount,
    LastEvaluationTime,
    LastActionType,
    LastReason,
    LastSensitiveInformation,
    SiteCollectionUrl,
    DLPRuleMatch_RawEventData,
    DLPRuleMatch_ReportId,
    SharingEvents

Explanation

This KQL (Kusto Query Language) query is designed to analyze and correlate data loss prevention (DLP) alerts and related events in a Microsoft SharePoint Online environment. Here's a simplified breakdown of what the query does:

  1. Define Variables:

    • alert_id: A placeholder for a specific alert ID.
    • query_period: The time range for the query, set to 1 day.
  2. Extract File Paths:

    • From the AlertEvidence table, it retrieves distinct file paths and names related to the specified alert ID within the last day.
  3. Retrieve Alert Events:

    • From the CloudAppEvents table, it fetches events where an alert was triggered or updated, matching the specified alert ID.
    • It extracts various details such as user information, severity, workload, policy details, and file owner information.
  4. Filter for SharePoint DLP Alerts:

    • It filters the events to only include those related to SharePoint and categorized as data loss prevention.
  5. Join with File Paths:

    • It performs a left outer join with the previously extracted file paths to correlate alert events with specific files.
  6. Analyze DLP Rule Matches:

    • It joins with additional CloudAppEvents to analyze rule matches and undos related to DLP policies.
    • It summarizes the evaluation events, capturing details like the last action type and sensitive information involved.
  7. Identify Sharing Events:

    • It further joins with events related to file sharing actions (e.g., added to a group or secure link) in SharePoint.
    • It aggregates sharing events to identify targets and operations related to the files involved in the DLP alerts.
  8. Project and Reorder Columns:

    • Finally, it selects and orders the columns to present a comprehensive view of the alert, including timestamps, user details, policy information, file paths, sensitive information, and sharing events.

In summary, this query is a complex analysis that correlates DLP alerts with file activities and sharing events in SharePoint, providing insights into potential data loss incidents and how files were shared or accessed.

Details

Jose Sebastián Canós profile picture

Jose Sebastián Canós

Released: May 7, 2025

Tables

AlertEvidenceCloudAppEvents

Keywords

AlertEvidenceCloudAppEventsSharePointDataLossPreventionFileUserPrincipalNameSensitiveInformationPolicyManagementRuleIdObjectNameSourceFilePathFileOwnerNameFileOwnerDisplayNameUSISharingEvents

Operators

letagostrcattodynamicdistincturl_decodetostringinsummarizearg_maxextendtolowersplitlookupprojectjoinhint.strategymv-expandiffarray_lengthmv-applymake_listbag_remove_keysarg_mindatetimeonproject-awayasmaterializedhasmake_set_ifisnotemptyarray_sort_ascmake_bagbag_packbag_pack_columnsarray_concattake_anysortascproject-reorder

Actions

GitHub