Query Details

35 CSL ZIA Low Slow Multi Channel Exfiltration

Query

id: f935a6b7-c8d9-4e0f-1a2b-3c4d5e6f7a8b
name: "Zscaler ZIA - Patient APT: Multi-Channel Low & Slow Exfiltration"
version: 1.0.0
kind: Scheduled
description: |
  Detects "low and slow" multi-channel data exfiltration by users who gradually upload data
  across non-corporate cloud storage, code-hosting, and messaging platforms over 30 days —
  staying below per-session DLP thresholds. The query aggregates ZIA upload bytes per user
  to non-corporate destinations (Google Drive, Dropbox, GitHub, WhatsApp, Telegram, etc.)
  combined with M365 OfficeActivity external-sharing events (anonymous links, SharingSet)
  over the same window.

  Users are flagged when their upload volume deviates ≥3 standard deviations from the peer
  group baseline, when external sharing events include anonymous public links, or when upload
  activity spans ≥20 days (persistence indicator).

  This technique is characteristic of APT and insider-threat scenarios where the attacker
  deliberately avoids large single transfers. The resulting output is a prioritised list
  of "siphon-like" identities for manual investigation.

  MITRE ATT&CK: TA0009 (Exfiltration), T1041 (Exfiltration Over C2 Channel),
  T1567 (Exfiltration Over Web Service), T1567.002 (Exfiltration to Cloud Storage).
severity: High
requiredDataConnectors:
  - connectorId: CommonSecurityEvents
    dataTypes:
      - CommonSecurityLog
queryFrequency: P1D
queryPeriod: P30D
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1041
  - T1567
query: |
    let HuntWindow   = 30d;
    let ExfilDomains = dynamic([
        "drive.google.com", "docs.google.com", "dropbox.com", "box.com",
        "mega.nz", "mediafire.com", "sendspace.com", "wetransfer.com",
        "4shared.com", "anonfiles.com",
        "github.com", "gitlab.com", "bitbucket.org", "gist.github.com",
        "web.whatsapp.com", "telegram.org", "t.me", "discord.com",
        "discordapp.com", "signal.org",
        "mail.google.com", "outlook.live.com", "protonmail.com", "tutanota.com"]);
    let ZIA_Daily =
        CommonSecurityLog
        | where TimeGenerated > ago(HuntWindow)
        | where DeviceVendor == "Zscaler"
        | where DeviceAction !in ("block", "BLOCK", "Blocked", "blocked", "deny")
        | where isnotempty(SourceUserName)
        | where DestinationHostName has_any (ExfilDomains)
        | summarize
            DaySentBytes    = sum(SentBytes),
            DayRequestCount = count(),
            DayDestinCount  = dcount(DestinationHostName)
          by UserName = tolower(SourceUserName), Day = bin(TimeGenerated, 1d);
    let ZIA_Users =
        ZIA_Daily
        | summarize
            ZIA_TotalMBSent   = round(toreal(sum(DaySentBytes)) / 1048576, 2),
            ZIA_ActiveDays    = dcount(Day),
            ZIA_TotalRequests = sum(DayRequestCount),
            ZIA_UniqueDestCnt = sum(DayDestinCount)
          by UserName;
    let globalAvg = toscalar(ZIA_Users | summarize avg(ZIA_TotalMBSent));
    let globalStd = toscalar(ZIA_Users | summarize stdev(ZIA_TotalMBSent));
    let O365_Sharing =
        OfficeActivity
        | where TimeGenerated > ago(HuntWindow)
        | where isnotempty(UserId)
        | where Operation in (
            "FileDownloaded", "FileSyncDownloadedFull", "FileCopied",
            "AnonymousLinkCreated", "AnonymousLinkUsed",
            "SharingSet", "SharingInvitationCreated",
            "AddedToSecureLink", "SecureLinkUsed")
        | summarize
            O365_EventCount = count(),
            O365_Operations = make_set(Operation, 10),
            O365_AnonLinks  = countif(Operation has "Anonymous"),
            O365_LastEvent  = max(TimeGenerated)
          by UserName = tolower(UserId);
    ZIA_Users
    | join kind=leftouter O365_Sharing on UserName
    | extend
        O365_EventCount = coalesce(O365_EventCount, 0),
        O365_AnonLinks  = coalesce(O365_AnonLinks, 0)
    | extend
        Zscore = iff(globalStd > 0,
                     round((ZIA_TotalMBSent - globalAvg) / globalStd, 2),
                     0.0),
        DistinctChannels = iff(ZIA_TotalMBSent > 5, 1, 0)
                         + iff(O365_EventCount  > 0, 1, 0)
    | where Zscore >= 3.0
        or (O365_AnonLinks  >= 3 and ZIA_TotalMBSent > 10)
        or (ZIA_ActiveDays  >= 20 and ZIA_TotalMBSent > 50)
    | project
        UserName,
        ZIA_TotalMBSent, ZIA_ActiveDays, ZIA_TotalRequests, ZIA_UniqueDestCnt,
        O365_EventCount, O365_AnonLinks, O365_Operations,
        DistinctChannels, Zscore
    | order by Zscore desc, ZIA_TotalMBSent desc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserName
customDetails:
  ZIA_TotalMBSent: ZIA_TotalMBSent
  ZIA_ActiveDays: ZIA_ActiveDays
  Zscore: Zscore
  O365_AnonLinks: O365_AnonLinks
  DistinctChannels: DistinctChannels
alertDetailsOverride:
  alertDisplayNameFormat: "Low & Slow Exfil Candidate - {{UserName}} (z={{Zscore}}, {{ZIA_TotalMBSent}} MB over {{ZIA_ActiveDays}} days)"
  alertDescriptionFormat: "User {{UserName}} uploaded {{ZIA_TotalMBSent}} MB to non-corporate destinations across {{ZIA_ActiveDays}} active days (z-score: {{Zscore}}). Anonymous links created: {{O365_AnonLinks}}."
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: true
    reopenClosedIncident: false
    lookbackDuration: P1D
    matchingMethod: Selected
    groupByEntities:
      - Account
    groupByAlertDetails: []
    groupByCustomDetails: []

Explanation

This query is designed to detect suspicious data exfiltration activities by users over a 30-day period. It focuses on identifying "low and slow" data transfers, where users gradually upload data to non-corporate cloud storage, code-hosting, and messaging platforms, avoiding detection by staying below typical data loss prevention (DLP) thresholds.

Here's a simplified breakdown of what the query does:

  1. Data Collection: It collects data from Zscaler logs and Microsoft 365 OfficeActivity logs over the past 30 days.

  2. Target Platforms: It looks for uploads to a list of non-corporate platforms like Google Drive, Dropbox, GitHub, WhatsApp, and others.

  3. User Activity Analysis:

    • Aggregates the total amount of data uploaded by each user.
    • Counts the number of days a user was active in uploading data.
    • Counts the number of different platforms a user uploaded data to.
  4. Anomaly Detection:

    • Calculates the average and standard deviation of data uploaded by all users.
    • Flags users whose upload volume is significantly higher (≥3 standard deviations) than the average.
    • Flags users who create anonymous public links or have upload activity spanning 20 or more days.
  5. Output: The query generates a prioritized list of users who exhibit suspicious behavior, potentially indicating advanced persistent threats (APT) or insider threats. These users are flagged for further manual investigation.

  6. Alerting: If any suspicious activity is detected, an alert is created with details about the user's activity, including the amount of data uploaded, the number of active days, and any anonymous links created.

The query is scheduled to run daily and is configured to create incidents for further investigation if any suspicious activity is detected.

Details

David Alonso profile picture

David Alonso

Released: March 2, 2026

Tables

CommonSecurityLogOfficeActivity

Keywords

CommonSecurityLogOfficeActivityUserNameUserIdDeviceVendorDeviceActionSourceUserNameDestinationHostNameOperationTimeGeneratedSentBytesDaySentBytesDayRequestCountDayDestinCountZIA_TotalMBSentZIA_ActiveDaysZIA_TotalRequestsZIA_UniqueDestCntO365_EventCountO365_OperationsO365_AnonLinksO365_LastEventZscoreDistinctChannels

Operators

letdynamicago!inisnotemptyhas_anysummarizesumcountdcounttolowerbinroundtorealtoscalaravgstdevinmake_setcountifmaxjoinkind=leftouterextendcoalesceiffwhereorprojectorder bydesc

Actions