Detection rules › Kusto

SharePointFileOperation via previously unseen IPs

Status
available
Severity
medium
Time window
14d
Group by
ClientIP, Operation, Site_Url, UserId
Source
github.com/Azure/Azure-Sentinel

Identifies anomalies using user behavior by setting a threshold for significant changes in file upload/download activities from new IP addresses. It establishes a baseline of typical behavior, compares it to recent activity, and flags deviations exceeding a default threshold of 25.

MITRE ATT&CK coverage

TacticTechniques
ExfiltrationT1030 Data Transfer Size Limits

Event coverage

Rules detecting the same action

Other rules on this platform that filter on the same API call or operation.

Rule body kusto

id: 4b11568b-3f5f-4ba1-80c8-7f1dc8390eb7
name: SharePointFileOperation via previously unseen IPs
description: |
  'Identifies anomalies using user behavior by setting a threshold for significant changes in file upload/download activities from new IP addresses. It establishes a baseline of typical behavior, compares it to recent activity, and flags deviations exceeding a default threshold of 25.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1030
query: |
  // Define a threshold for significant deviations
  let threshold = 25;
  // Define the name for the SharePoint File Operation record type
  let szSharePointFileOperation = "SharePointFileOperation";
  // Define an array of SharePoint operations of interest
  let szOperations = dynamic(["FileDownloaded", "FileUploaded"]);
  // Define the start and end time for the analysis period
  let starttime = 14d;
  let endtime = 1d;
  // Define a baseline of normal user behavior
  let userBaseline = OfficeActivity
  | where TimeGenerated between(ago(starttime)..ago(endtime))
  | where RecordType =~ szSharePointFileOperation
  | where Operation in~ (szOperations)
  | where isnotempty(UserAgent)
  | summarize Count = count() by UserId, Operation, Site_Url, ClientIP
  | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIP;
  // Get recent user activity
  let recentUserActivity = OfficeActivity
  | where TimeGenerated > ago(endtime)
  | where RecordType =~ szSharePointFileOperation
  | where Operation in~ (szOperations)
  | where isnotempty(UserAgent)
  | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIP, OfficeObjectId, OfficeWorkload, UserAgent;
  // Join the baseline and recent activity, and calculate the deviation
  let UserBehaviorAnalysis = userBaseline | join kind=inner (recentUserActivity) on UserId, Operation, Site_Url, ClientIP
  | extend Deviation = abs(RecentCount - AvgCount) / AvgCount;
  // Filter for significant deviations
  UserBehaviorAnalysis
  | where Deviation > threshold
  | project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIP, Site_Url, OfficeObjectId, OfficeWorkload, UserAgent, Deviation, Count=RecentCount
  | order by Count desc, ClientIP asc, Operation asc, UserId asc
  | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserId
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Site_Url
version: 2.0.4
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 25;
let szSharePointFileOperation = "SharePointFileOperation";
let szOperations = dynamic(["FileDownloaded", "FileUploaded"]);
let starttime = 14d;
let endtime = 1d;

Let binding: recentUserActivity

let recentUserActivity = OfficeActivity
| where TimeGenerated > ago(endtime)
| where RecordType =~ szSharePointFileOperation
| where Operation in~ (szOperations)
| where isnotempty(UserAgent)
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIP, OfficeObjectId, OfficeWorkload, UserAgent;

Derived from szSharePointFileOperation, szOperations, endtime.

The stages below define let UserBehaviorAnalysis (the rule's main pipeline source).

Stage 1: source

OfficeActivity

Stage 2: where

| where TimeGenerated between(ago(starttime)..ago(endtime))

Stage 3: where

| where RecordType =~ szSharePointFileOperation

Stage 4: where

| where Operation in~ (szOperations)

Stage 5: where

| where isnotempty(UserAgent)

Stage 6: summarize

| summarize Count = count() by UserId, Operation, Site_Url, ClientIP

Stage 7: summarize

| summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIP

Stage 8: join

| join kind=inner (recentUserActivity) on UserId, Operation, Site_Url, ClientIP

Stage 9: extend

| extend Deviation = abs(RecentCount - AvgCount) / AvgCount

The stages below run on UserBehaviorAnalysis (the outer pipeline).

Stage 10: where

UserBehaviorAnalysis
| where Deviation > threshold

Stage 11: project

| project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIP, Site_Url, OfficeObjectId, OfficeWorkload, UserAgent, Deviation, Count=RecentCount

Stage 12: sort

| order by Count desc, ClientIP asc, Operation asc, UserId asc

Stage 13: extend

| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])

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
Deviationgt
  • 25 transforms: cased
Operationin
  • FileDownloaded
  • FileUploaded
RecordTypeeq
  • SharePointFileOperation
UserAgentis_not_null
  • (no value, null check)

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
ClientIPproject
Countproject
Deviationproject
EndTimeUtcproject
OfficeObjectIdproject
OfficeWorkloadproject
Operationproject
Site_Urlproject
StartTimeUtcproject
UserAgentproject
UserIdproject
UserTypeproject
AccountNameextend
AccountUPNSuffixextend