Detection rules › Kusto
SharePointFileOperation via devices with previously unseen user agents
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
| Tactic | Techniques |
|---|---|
| Exfiltration | T1030 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.
| Field | Kind | Values |
|---|---|---|
Deviation | gt |
|
Operation | in |
|
RecordType | eq |
|
UserAgent | in |
|
UserAgent | is_not_null |
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.
| Field | Source |
|---|---|
AvgCount | summarize |
Operation | summarize |
Site_Url | summarize |
UserId | summarize |
Deviation | extend |
UserIdName | extend |
UserIdUPNSuffix | extend |