Detection rules › Kusto

Suspicious access of BEC related documents

Severity
medium
Time window
14d
Group by
TimeStamp, User
Source
github.com/Azure/Azure-Sentinel

This query looks for users with suspicious spikes in the number of files accessed that relate to topics commonly accessed as part of Business Email Compromise (BEC) attacks. The query looks for access to files in storage that relate to topics such as invoices or payments, and then looks for users accessing these files in significantly higher numbers than in the previous 14 days. Incidents raised by this analytic should be investigated to see if the user accessing these files should be accessing them, and if the volume they accessed them at was related to a legitimate business need. This query contains thresholds to reduce the chance of false positives, these can be adjusted to suit individual environments. In addition false positives could be generated by legitimate, scheduled actions that occur less often than every 14 days, additional exclusions can be added for these actions on username or IP address entities. This query uses the imFileEvent schema from ASIM, you will first need to ensure you have ASIM deployed in your environment. Ref https://learn.microsoft.com/azure/sentinel/normalization-about-parsers

MITRE ATT&CK coverage

TacticTechniques
Collection

Telemetry coverage

Rule body

id: cd8d946d-10a4-40a9-bac1-6d0a6c847d65
name: Suspicious access of BEC related documents
description: |
  'This query looks for users with suspicious spikes in the number of files accessed that relate to topics commonly accessed as part of Business Email Compromise (BEC) attacks.
  The query looks for access to files in storage that relate to topics such as invoices or payments, and then looks for users accessing these files in significantly higher numbers than in the previous 14 days. Incidents raised by this analytic should be investigated to see if the user accessing these files should be accessing them, and if the volume they accessed them at was related to a legitimate business need. 
  This query contains thresholds to reduce the chance of false positives, these can be adjusted to suit individual environments. In addition false positives could be generated by legitimate, scheduled actions that occur less often than every 14 days, additional exclusions can be added for these actions on username or IP address entities. This query uses the imFileEvent schema from ASIM, you will first need to ensure you have ASIM deployed in your environment. Ref https://learn.microsoft.com/azure/sentinel/normalization-about-parsers'
severity: Medium
requiredDataConnectors: []
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Collection
relevantTechniques:
  - T1530
eventGroupingSettings:
  aggregationKind: SingleAlert
query: |
  let BEC_Keywords = dynamic([ 'invoice','payment','paycheck','transfer','bank statement','bank details','closing','funds','bank account','account details','remittance','purchase','deposit',"PO#","Zahlung","Rechnung","Paiement", "virement bancaire","Bankuberweisung",'hacked','phishing']);
  // Adjust this threshold based on your environment
  let sensitivity = 2.5;
  let Events = materialize(imFileEvent
  | where TimeGenerated between(startofday(ago(14d))..endofday(ago(0d)))
  | where User !~ "app@sharepoint"
  | where EventType =~ "FileAccessed"
  | extend OriginalEvent = column_ifexists("EventOriginalType","Unknown")
  | where OriginalEvent !~ "FileSyncDownloadedFull"
  | where EventProduct in ("SharePoint 365", "Azure File Storage", "OneDrive" , "SharePoint")
  | where FilePath has_any(BEC_Keywords)
  | extend _AuthDetails = column_ifexists("AuthorizationDetails", "None")
  | extend SPuser = case(gettype(_AuthDetails) == "array", tostring(todynamic(_AuthDetails)[0].principals[0].id), "Unknown")
  | extend User = case(isnotempty(User), User, SPuser)
  | where isnotempty(User));
  Events
  | summarize dcount(FileName) by User, bin(startofday(TimeGenerated), 1d)
  | summarize CountOfDocs = make_list(dcount_FileName, 10000), TimeStamp = make_list(TimeGenerated, 10000) by User
  | extend (Anomalies, Score, Baseline) = series_decompose_anomalies(CountOfDocs, sensitivity, -1, 'linefit')
  | mv-expand CountOfDocs to typeof(double), TimeStamp to typeof(datetime), Anomalies to typeof(double), Score to typeof(double), Baseline to typeof(long)
  | where Anomalies > 0
  | project TimeStamp, CountOfDocs, Baseline, Score, Anomalies, User
  | join kind=inner(Events | extend TimeStamp = startofday(TimeGenerated)) on TimeStamp, User
  | extend IpAddr = column_ifexists("IpAddr", SrcIpAddr)
  | extend Name = iif(User contains "@", split(User, "@")[0], split(User, "\\")[1])
  | extend UPNSuffix = iif(User contains "@", split(User, "@")[1], "")
  | extend NTDomain = iif(User contains "@", split(User, "\\")[0], "")
  | project-reorder TimeGenerated, User, EventType, EventResult, EventProduct, FilePath, HttpUserAgent, IpAddr, CountOfDocs, Baseline, Score
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: User
      - identifier: Name
        columnName: Name
      - identifier: NTDomain
        columnName: NTDomain   
  - entityType: Account
    fieldMappings:        
      - identifier: AadUserId
        columnName: User
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpAddr
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FilePath
customDetails:
  Type: EventType
  Result: EventResult
  Product: EventProduct
  UserAgent: HttpUserAgent
alertDetailsOverride:
  alertDisplayNameFormat: Suspicious access of {{CountOfDocs}} BEC related documents by {{User}}
  alertDescriptionFormat: |
    This query looks for users (in this case {{User}}) with suspicious spikes in the number of files accessed (in this case {{CountOfDocs}} events) that relate to topics commonly accessed as part of Business Email Compromise (BEC) attacks. The query looks for access to files in storage that relate to topics such as invoices or payments, and then looks for users accessing these files in significantly higher numbers than in the previous 14 days. Incidents raised by this analytic should be investigated to see if the user accessing these files should be accessing them, and if the volume they accessed them at was related to a legitimate business need. 
    This query contains thresholds to reduce the chance of false positives, these can be adjusted to suit individual environments. In addition false positives could be generated by legitimate, scheduled actions that occur less often than every 14 days, additional exclusions can be added for these actions on username or IP address entities. This query uses the imFileEvent schema from ASIM, you will first need to ensure you have ASIM deployed in your environment. Ref https://learn.microsoft.com/azure/sentinel/normalization-about-parsers
version: 1.0.5
kind: Scheduled

Stages and Predicates

Parameters

let sensitivity = 2.5;

let Events is inlined into the numbered stages below.

Let binding: BEC_Keywords

let BEC_Keywords = dynamic([ 'invoice','payment','paycheck','transfer','bank statement','bank details','closing','funds','bank account','account details','remittance','purchase','deposit',"PO#","Zahlung","Rechnung","Paiement", "virement bancaire","Bankuberweisung",'hacked','phishing']);

Stage 1: source

let Events

Stage 2: source

imFileEvent

Stage 3: where

where TimeGenerated between (ago(1209600s) .. now())

Stage 4: where

where User !~ "app@sharepoint"

Stage 5: where

where EventType =~ "FileAccessed"

Stage 6: extend

extend OriginalEvent

Stage 7: where

where OriginalEvent !~ "FileSyncDownloadedFull"

Stage 8: where

where EventProduct in~ ("Azure File Storage", "OneDrive", "SharePoint", "SharePoint 365")

Stage 9: where

where (FilePath contains "invoice" or FilePath contains "payment" or FilePath contains "paycheck" or FilePath contains "transfer" or FilePath contains "bank statement" or FilePath contains "bank details" or FilePath contains "closing" or FilePath contains "funds" or FilePath contains "bank account" or FilePath contains "account details" or FilePath contains "remittance" or FilePath contains "purchase" or FilePath contains "deposit" or FilePath contains "PO#" or FilePath contains "Zahlung" or FilePath contains "Rechnung" or FilePath contains "Paiement" or FilePath contains "virement bancaire" or FilePath contains "Bankuberweisung" or FilePath contains "hacked" or FilePath contains "phishing")

Stage 10: extend (3 consecutive steps)

extend SPuser, User, _AuthDetails

Stage 11: where

where isnotempty(User)

Stage 12: summarize

summarize by User

Stage 13: summarize

summarize CountOfDocs, TimeStamp by User

Stage 14: extend

extend Anomalies, Baseline, Score

Stage 15: mv-expand

mv-expand CountOfDocs

Stage 16: where

where Anomalies > 0

Stage 17: project

project Anomalies, Baseline, CountOfDocs, Score, TimeStamp, User

Stage 18: join

join kind=inner (Events) on TimeStamp, User

Stage 19: extend (4 consecutive steps)

extend IpAddr, NTDomain, Name, UPNSuffix

Stage 20: project-reorder

project-reorder

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Anomaliesgt
  • 0 corpus 6 (kusto 6)
field:"Anomalies" kind:gt value:"0"
EventProductin
  • Azure File Storage
  • OneDrive
  • SharePoint
  • SharePoint 365
field:"EventProduct" kind:in
EventTypeeq
  • FileAccessed
field:"OperationName" kind:eq value:"FileAccessed"
FilePathmatch
  • Bankuberweisung transforms: term corpus 2 (kusto 2)
  • PO# transforms: term corpus 2 (kusto 2)
  • Paiement transforms: term corpus 2 (kusto 2)
  • Rechnung transforms: term corpus 2 (kusto 2)
  • Zahlung transforms: term corpus 2 (kusto 2)
  • account details transforms: term corpus 2 (kusto 2)
  • bank account transforms: term corpus 2 (kusto 2)
  • bank details transforms: term corpus 2 (kusto 2)
  • bank statement transforms: term corpus 2 (kusto 2)
  • closing transforms: term corpus 2 (kusto 2)
  • deposit transforms: term corpus 2 (kusto 2)
  • funds transforms: term corpus 2 (kusto 2)
  • hacked transforms: term corpus 2 (kusto 2)
  • invoice transforms: term corpus 2 (kusto 2)
  • paycheck transforms: term corpus 2 (kusto 2)
  • payment transforms: term corpus 2 (kusto 2)
  • phishing transforms: term corpus 2 (kusto 2)
  • purchase transforms: term corpus 2 (kusto 2)
  • remittance transforms: term corpus 2 (kusto 2)
  • transfer transforms: term corpus 2 (kusto 2)
  • virement bancaire transforms: term corpus 2 (kusto 2)
field:"FilePath" kind:match
OriginalEventne
  • FileSyncDownloadedFull
field:"OriginalEvent" kind:ne value:"FileSyncDownloadedFull"
Useris_not_null
  • (no value, null check)
field:"user" kind:is_not_null
Userne
  • app@sharepoint corpus 2 (splunk 1, kusto 1)
field:"user" kind:ne value:"app@sharepoint"

Output fields

These fields are emitted when the rule matches.

FieldSource
Anomaliesproject
Baselineproject
CountOfDocsproject
Scoreproject
TimeStampproject
Userproject
IpAddrextend
Nameextend
UPNSuffixextend
NTDomainextend