Detection rules › Kusto

Netskope - Data Movement Tracking (Upload/Download Monitoring)

Status
available
Severity
informational
Time window
1h
Group by
CsUsername, XCCountry, XCDevice
Source
github.com/Azure/Azure-Sentinel

Tracks file uploads and downloads, monitoring data movement direction, size, and destination. Provides visibility into data flow patterns.

MITRE ATT&CK coverage

TacticTechniques
CollectionT1074 Data Staged
ExfiltrationT1567 Exfiltration Over Web Service

Rule body kusto

id: cf103180-cb81-4796-921d-3cc7eef4e817
name: Netskope - Data Movement Tracking (Upload/Download Monitoring)
description: |
  Tracks file uploads and downloads, monitoring data movement direction, size, and destination. Provides visibility into data flow patterns.
severity: Informational
status: Available
requiredDataConnectors:
  - connectorId: NetskopeWebTxConnector
    dataTypes:
      - NetskopeWebTransactions_CL
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - Collection
relevantTechniques:
  - T1567
  - T1074
query: |
  let significantSizeMB = 50;
  NetskopeWebTransactions_CL
  | where TimeGenerated > ago(1h)
  | where isnotempty(CsUsername)
  | where CsBytes > 0 or ScBytes > 0 or XRsFileSize > 0
  | extend 
      Direction = case(
          CsBytes > ScBytes, 'Upload',
          ScBytes > CsBytes, 'Download',
          'Unknown'),
      TransferSize = max_of(CsBytes, ScBytes, XRsFileSize)
  | summarize 
      TotalUploadBytes = sumif(CsBytes, Direction == 'Upload'),
      TotalDownloadBytes = sumif(ScBytes, Direction == 'Download'),
      UploadCount = countif(Direction == 'Upload'),
      DownloadCount = countif(Direction == 'Download'),
      UniqueFiles = dcount(XCsAppObjectName),
      Files = make_set(XCsAppObjectName, 20),
      FileTypes = make_set(XRsFileType),
      Apps = make_set(XCsApp),
      Destinations = make_set(CsHost, 20)
      by CsUsername, XCDevice, XCCountry, bin(TimeGenerated, 1h)
  | extend 
      TotalUploadMB = round(TotalUploadBytes / 1048576.0, 2),
      TotalDownloadMB = round(TotalDownloadBytes / 1048576.0, 2),
      TotalTransferMB = round((TotalUploadBytes + TotalDownloadBytes) / 1048576.0, 2)
  | where TotalTransferMB > significantSizeMB
  | extend DataFlowSummary = strcat('Upload: ', TotalUploadMB, ' MB (', UploadCount, ' ops), Download: ', TotalDownloadMB, ' MB (', DownloadCount, ' ops)')
  | project 
      TimeGenerated,
      User = CsUsername,
      Device = XCDevice,
      Country = XCCountry,
      TotalUploadMB,
      TotalDownloadMB,
      TotalTransferMB,
      UploadOperations = UploadCount,
      DownloadOperations = DownloadCount,
      UniqueFiles,
      FilesSample = Files,
      FileTypes,
      Applications = Apps,
      Destinations,
      DataFlowSummary
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: User
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let significantSizeMB = 50;

Stage 1: source

NetskopeWebTransactions_CL

Stage 2: where

| where TimeGenerated > ago(1h)

Stage 3: where

| where isnotempty(CsUsername)

Stage 4: where

| where CsBytes > 0 or ScBytes > 0 or XRsFileSize > 0

Stage 5: extend

| extend 
    Direction = case(
        CsBytes > ScBytes, 'Upload',
        ScBytes > CsBytes, 'Download',
        'Unknown'),
    TransferSize = max_of(CsBytes, ScBytes, XRsFileSize)
Direction =
ifCsBytes > "ScBytes"'Upload'
elifScBytes > "CsBytes"'Download'
else'Unknown'

Stage 6: summarize

| summarize 
    TotalUploadBytes = sumif(CsBytes, Direction == 'Upload'),
    TotalDownloadBytes = sumif(ScBytes, Direction == 'Download'),
    UploadCount = countif(Direction == 'Upload'),
    DownloadCount = countif(Direction == 'Download'),
    UniqueFiles = dcount(XCsAppObjectName),
    Files = make_set(XCsAppObjectName, 20),
    FileTypes = make_set(XRsFileType),
    Apps = make_set(XCsApp),
    Destinations = make_set(CsHost, 20)
    by CsUsername, XCDevice, XCCountry, bin(TimeGenerated, 1h)

Stage 7: extend

| extend 
    TotalUploadMB = round(TotalUploadBytes / 1048576.0, 2),
    TotalDownloadMB = round(TotalDownloadBytes / 1048576.0, 2),
    TotalTransferMB = round((TotalUploadBytes + TotalDownloadBytes) / 1048576.0, 2)

Stage 8: where

| where TotalTransferMB > significantSizeMB

Stage 9: extend

| extend DataFlowSummary = strcat('Upload: ', TotalUploadMB, ' MB (', UploadCount, ' ops), Download: ', TotalDownloadMB, ' MB (', DownloadCount, ' ops)')

Stage 10: project

| project 
    TimeGenerated,
    User = CsUsername,
    Device = XCDevice,
    Country = XCCountry,
    TotalUploadMB,
    TotalDownloadMB,
    TotalTransferMB,
    UploadOperations = UploadCount,
    DownloadOperations = DownloadCount,
    UniqueFiles,
    FilesSample = Files,
    FileTypes,
    Applications = Apps,
    Destinations,
    DataFlowSummary

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
CsBytesgt
  • 0 transforms: cased
CsUsernameis_not_null
  • (no value, null check)
ScBytesgt
  • 0 transforms: cased
TotalTransferMBgt
  • 50 transforms: cased
XRsFileSizegt
  • 0 transforms: cased

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
Applicationsproject
Countryproject
DataFlowSummaryproject
Destinationsproject
Deviceproject
DownloadOperationsproject
FileTypesproject
FilesSampleproject
TimeGeneratedproject
TotalDownloadMBproject
TotalTransferMBproject
TotalUploadMBproject
UniqueFilesproject
UploadOperationsproject
Userproject