Detection rules › Kusto

SharePointFileOperation via devices with previously unseen user agents

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

Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 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: 5dd76a87-9f87-4576-bab3-268b0e2b338b
name: SharePointFileOperation via devices with previously unseen user agents
description: |
  'Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25).'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1030
query: |
  // Set threshold for the number of downloads/uploads from a new user agent
  let threshold = 5;
  // Define constants for SharePoint file operations
  let szSharePointFileOperation = "SharePointFileOperation";
  let szOperations = dynamic(["FileDownloaded", "FileUploaded"]);
  // Define the historical activity for analysis
  let starttime = 14d; // Define the start time for historical data (14 days ago)
  let endtime = 1d;   // Define the end time for historical data (1 day ago)
  // Extract the base events for analysis
  let Baseevents =
    OfficeActivity
    | where TimeGenerated between (ago(starttime) .. ago(endtime))
    | where RecordType =~ szSharePointFileOperation
    | where Operation in~ (szOperations)
    | where isnotempty(UserAgent);
  // Identify frequently occurring user agents
  let FrequentUA = Baseevents
    | summarize FUACount = count() by UserAgent, RecordType, Operation
    | where FUACount >= threshold
    | distinct UserAgent;
  // Calculate a user baseline for further analysis
  let UserBaseLine = Baseevents
    | summarize Count = count() by UserId, Operation, Site_Url
    | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url;
  // Extract recent activity for analysis
  let RecentActivity = OfficeActivity
    | where TimeGenerated > ago(endtime)
    | where RecordType =~ szSharePointFileOperation
    | where Operation in~ (szOperations)
    | where isnotempty(UserAgent)
    | where UserAgent in~ (FrequentUA)
    | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() 
    by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url;
  // Analyze user behavior based on baseline and recent activity
  let UserBehaviorAnalysis = UserBaseLine
    | join kind=inner (RecentActivity) on UserId, Operation, Site_Url
    | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount;
  // Filter and format results for specific user behavior analysis
  UserBehaviorAnalysis
    | where Deviation > 25
    | extend UserIdName = tostring(split(UserId, '@')[0]), UserIdUPNSuffix = tostring(split(UserId, '@')[1])
    | project-reorder StartTime, EndTime, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url
    | project-away Site_Url1, UserId1, Operation1
    | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserId
      - identifier: Name
        columnName: UserIdName
      - identifier: UPNSuffix
        columnName: UserIdUPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Site_Url
version: 2.2.4
kind: Scheduled

Stages and Predicates

Parameters

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

Let binding: FrequentUA

let FrequentUA = Baseevents
  | summarize FUACount = count() by UserAgent, RecordType, Operation
  | where FUACount >= threshold
  | distinct UserAgent;

Derived from threshold, Baseevents.

Let binding: RecentActivity

let RecentActivity = OfficeActivity
  | where TimeGenerated > ago(endtime)
  | where RecordType =~ szSharePointFileOperation
  | where Operation in~ (szOperations)
  | where isnotempty(UserAgent)
  | where UserAgent in~ (FrequentUA)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() 
  by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url;

Derived from szSharePointFileOperation, szOperations, endtime, FrequentUA.

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

Stage 7: summarize

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

Stage 8: join

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

Stage 9: extend

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

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

Stage 10: where

UserBehaviorAnalysis
| where Deviation > 25

Stage 11: extend

| extend UserIdName = tostring(split(UserId, '@')[0]), UserIdUPNSuffix = tostring(split(UserId, '@')[1])

Stage 12: project-reorder

| project-reorder StartTime, EndTime, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url

Stage 13: project-away

| project-away Site_Url1, UserId1, Operation1

Stage 14: sort

| order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc

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
UserAgentin
  • FrequentUA
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
AvgCountsummarize
Operationsummarize
Site_Urlsummarize
UserIdsummarize
Deviationextend
UserIdNameextend
UserIdUPNSuffixextend