Detection rules › Kusto

New executable via Office FileUploaded Operation

Status
available
Severity
low
Time window
8d
Group by
ClientIP, OfficeWorkload, Operation, RecordType, SiteUrlUserFolder, Site_Url, UserId, UserIdDiffThanUserFolder, UserIdUserFolderFormat, UserKey, UserType
Source
github.com/Azure/Azure-Sentinel

Identifies when executable file types are uploaded to Office services such as SharePoint and OneDrive. List currently includes 'exe', 'inf', 'gzip', 'cmd', 'bat' file extensions. Additionally, identifies when a given user is uploading these files to another users workspace. This may be indication of a staging location for malware or other malicious activity.

MITRE ATT&CK coverage

TacticTechniques
Lateral MovementT1570 Lateral Tool Transfer
Command & ControlT1105 Ingress Tool Transfer

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: d722831e-88f5-4e25-b106-4ef6e29f8c13
name: New executable via Office FileUploaded Operation
description: |
  'Identifies when executable file types are uploaded to Office services such as SharePoint and OneDrive.
  List currently includes 'exe', 'inf', 'gzip', 'cmd', 'bat' file extensions.
  Additionally, identifies when a given user is uploading these files to another users workspace.
  This may be indication of a staging location for malware or other malicious activity.'
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity (SharePoint) 
queryFrequency: 1d
queryPeriod: 8d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CommandAndControl
  - LateralMovement
relevantTechniques:
  - T1105
  - T1570
query: |
  // a threshold can be enabled, see commented line below for PrevSeenCount
  let threshold = 2;
  let uploadOp = 'FileUploaded';
  // Extensions that are interesting. Add/Remove to this list as you see fit
  let execExt = dynamic(['exe', 'inf', 'gzip', 'cmd', 'bat']);
  let starttime = 8d;
  let endtime = 1d;
  OfficeActivity | where TimeGenerated >= ago(endtime)
  // Limited to File Uploads due to potential noise, comment out the Operation statement below to include any operation type
  // Additional, but potentially noisy operation types that include Uploads and Downloads can be included by adding the following - Operation contains "upload" or Operation contains "download"
  | where Operation =~ uploadOp
  | where SourceFileExtension has_any (execExt)
  | project TimeGenerated, OfficeId, OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, UserAgent, Site_Url, SourceRelativeUrl, SourceFileName
  | join kind= leftanti (
  OfficeActivity | where TimeGenerated between (ago(starttime) .. ago(endtime))
  | where Operation =~ uploadOp
  | where SourceFileExtension has_any (execExt)
  | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000) , PrevSeenCount = count() by SourceFileName
  // To exclude previous matches when only above a specific count, change threshold above and uncomment the line below
  //| where PrevSeenCount > threshold
  | mvexpand SourceRelativeUrl, UserId
  | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId)
  ) on SourceFileName, SourceRelativeUrl, UserId
  | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])
  | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_'))
  // identify when UserId is not a match to the specific site url personal folder reference
  | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true , false )
  | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated),
  UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000)
  by OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder
  | 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
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FileNames       
version: 2.0.5
kind: Scheduled

Stages and Predicates

Parameters

let threshold = 2;
let uploadOp = 'FileUploaded';
let execExt = dynamic(['exe', 'inf', 'gzip', 'cmd', 'bat']);
let starttime = 8d;
let endtime = 1d;

Stage 1: source

OfficeActivity

Stage 2: where

| where TimeGenerated >= ago(endtime)

Stage 3: where

| where Operation =~ uploadOp

Stage 4: where

| where SourceFileExtension has_any (execExt)

Stage 5: project

| project TimeGenerated, OfficeId, OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, UserAgent, Site_Url, SourceRelativeUrl, SourceFileName

Stage 6: join (negated)

| join kind= leftanti (
OfficeActivity | where TimeGenerated between (ago(starttime) .. ago(endtime))
| where Operation =~ uploadOp
| where SourceFileExtension has_any (execExt)
| summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000) , PrevSeenCount = count() by SourceFileName
| mvexpand SourceRelativeUrl, UserId
| extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId)
) on SourceFileName, SourceRelativeUrl, UserId

Stage 7: extend (3 consecutive steps)

| extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])
| extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_'))
| extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true , false )

Stage 8: summarize

| summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated),
UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000)
by OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder

Stage 9: extend

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

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
OperationeqFileUploaded
SourceFileExtensionmatchexe, inf, gzip, cmd, bat

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
Operationeq
  • FileUploaded
SourceFileExtensionmatch
  • bat
  • cmd
  • exe
  • gzip
  • inf

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
ClientIPsummarize
EndTimesummarize
FileNamessummarize
OfficeIdssummarize
OfficeWorkloadsummarize
Operationsummarize
RecordTypesummarize
SiteUrlUserFoldersummarize
Site_Urlsummarize
SourceRelativeUrlssummarize
StartTimesummarize
TimeGeneratedsummarize
UserAgentssummarize
UserIdsummarize
UserIdDiffThanUserFoldersummarize
UserIdUserFolderFormatsummarize
UserKeysummarize
UserTypesummarize
AccountNameextend
AccountUPNSuffixextend