Detection rules › Kusto

Dataverse - Executable uploaded to SharePoint document management site

Status
available
Severity
low
Time window
14d
Group by
SharePointUrl, Site_Url
Source
github.com/Azure/Azure-Sentinel

Identifies executable files and scripts uploaded to SharePoint sites used for Dynamics document management, circumventing native file extension restrictions in Dataverse.

MITRE ATT&CK coverage

TacticTechniques
ExecutionT0863 User Execution
PersistenceT0873 Project File Infection

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: ba5e608f-7879-4927-8b0d-a9948b4fe6f3
kind: Scheduled
name: Dataverse - Executable uploaded to SharePoint document management site
description: Identifies executable files and scripts uploaded to SharePoint sites
  used for Dynamics document management, circumventing native file extension restrictions
  in Dataverse.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Office365
    dataTypes:
      - OfficeActivity (SharePoint)
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - Persistence
relevantTechniques:
  - T0863
  - T0873
query: |
  let file_extensions = dynamic(['com', 'exe', 'bat', 'cmd', 'vbs', 'vbe', 'js', 'jse', 'wsf', 'wsh', 'msc', 'cpl', 'ps1', 'scr']);
  let query_frequency = 1h;
  DataverseSharePointSites
  | join kind=inner (
      OfficeActivity
      | where TimeGenerated >= ago(query_frequency)
      | where OfficeWorkload == "SharePoint" and Operation == "FileUploaded")
      on $left.SharePointUrl == $right.Site_Url
  | where SourceFileExtension in (file_extensions)
  | extend
      CloudAppId = int(32780),
      SharePointId = int(20892),
      AccountName = tostring(split(UserId, '@')[0]),
      UPNSuffix = tostring(split(UserId, '@')[1])
  | project
      TimeGenerated,
      UserId,
      ClientIP,
      InstanceUrl,
      SourceFileName,
      SharePointUrl,
      CloudAppId,
      SharePointId,
      AccountName,
      UPNSuffix
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIP
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: SourceFileName
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: SharePointId
      - identifier: InstanceName
        columnName: SharePointUrl
alertDetailsOverride:
  alertDisplayNameFormat: Dataverse - Executable files uploaded in document management
    for {{InstanceUrl}}
  alertDescriptionFormat: Executable/script {{SourceFileName}} was uploaded by {{UserId}}
    in SharePoint site {{SharePointUrl}}
version: 3.2.0

Stages and Predicates

Parameters

let file_extensions = dynamic(['com', 'exe', 'bat', 'cmd', 'vbs', 'vbe', 'js', 'jse', 'wsf', 'wsh', 'msc', 'cpl', 'ps1', 'scr']);
let query_frequency = 1h;

Stage 1: source

DataverseSharePointSites

Stage 2: join

| join kind=inner (
    OfficeActivity
    | where TimeGenerated >= ago(query_frequency)
    | where OfficeWorkload == "SharePoint" and Operation == "FileUploaded")
    on $left.SharePointUrl == $right.Site_Url

Stage 3: where

| where SourceFileExtension in (file_extensions)

Stage 4: extend

| extend
    CloudAppId = int(32780),
    SharePointId = int(20892),
    AccountName = tostring(split(UserId, '@')[0]),
    UPNSuffix = tostring(split(UserId, '@')[1])

Stage 5: project

| project
    TimeGenerated,
    UserId,
    ClientIP,
    InstanceUrl,
    SourceFileName,
    SharePointUrl,
    CloudAppId,
    SharePointId,
    AccountName,
    UPNSuffix

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
OfficeWorkloadeq
  • SharePoint transforms: cased
Operationeq
  • FileUploaded transforms: cased
SourceFileExtensionin
  • bat transforms: cased
  • cmd transforms: cased
  • com transforms: cased
  • cpl transforms: cased
  • exe transforms: cased
  • js transforms: cased
  • jse transforms: cased
  • msc transforms: cased
  • ps1 transforms: cased
  • scr transforms: cased
  • vbe transforms: cased
  • vbs transforms: cased
  • wsf transforms: cased
  • wsh 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
AccountNameproject
ClientIPproject
CloudAppIdproject
InstanceUrlproject
SharePointIdproject
SharePointUrlproject
SourceFileNameproject
TimeGeneratedproject
UPNSuffixproject
UserIdproject