Detection rules › Kusto

Suspicious access of BEC related documents in AWS S3 buckets

Severity
medium
Time window
14d
Group by
TimeStamp, UserIdentityPrincipalid
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 AWS S3 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.

MITRE ATT&CK coverage

TacticTechniques
Collection

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

id: f3e2d35f-1202-4215-995c-4654ef07d1d8
name: Suspicious access of BEC related documents in AWS S3 buckets
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 AWS S3 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.'
severity: Medium
requiredDataConnectors:
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
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(AWSCloudTrail
  | where TimeGenerated between (ago(14d)..ago(0d))
  | where UserIdentityAccountId != "anonymous"
  | where EventSource startswith "s3."
  | where EventName =~ "GetObject"
  | extend FilePath = tostring(parse_json(RequestParameters).key)
  | where FilePath has_any(BEC_Keywords)
  );
  Events
  | summarize dcount(FilePath) by UserIdentityPrincipalid, bin(startofday(TimeGenerated), 1d)
  | summarize CountOfDocs = make_list(dcount_FilePath, 10000), TimeStamp = make_list(TimeGenerated, 10000) by UserIdentityPrincipalid
  | 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, UserIdentityPrincipalid
  | join kind=inner(Events | extend TimeStamp = startofday(TimeGenerated)) on TimeStamp, UserIdentityPrincipalid
  | extend Name = iif(UserIdentityUserName contains "@", split(UserIdentityUserName, "@")[0], UserIdentityUserName)
  | extend UPNSuffix = iif(UserIdentityUserName contains "@", split(UserIdentityUserName, "@")[1], "")
  | project-reorder TimeGenerated, UserIdentityType, UserIdentityPrincipalid, UserIdentityUserName, FilePath, EventName, UserAgent, SourceIpAddress, CountOfDocs, Baseline, Score
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserIdentityUserName
      - identifier: Name
        columnName: Name
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIpAddress
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: FilePath
customDetails:
  UserType: UserIdentityType
  Event: EventName
  UserAgent: UserAgent
alertDetailsOverride:
  alertDisplayNameFormat: Suspicious access of {{CountOfDocs}} BEC related documents in AWS S3 buckets by {{UserIdentityUserName}}
  alertDescriptionFormat: |
    This query looks for users (in this case {{UserIdentityUserName}}) with suspicious spikes in the number of files accessed (in this case {{CountOfDocs}})that relate to topics commonly accessed as part of Business Email Compromise (BEC) attacks. The query looks for access to files in AWS S3 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.
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let sensitivity = 2.5;

let Events is inlined into the numbered stages below.

Let binding: BEC_Keywords used in Stage 7

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']);

Stages 1 to 7 define let Events (the rule's main pipeline source); stages 8 to 17 run on it.

Stage 1: source

AWSCloudTrail

Stage 2: where

| where TimeGenerated between (ago(14d)..ago(0d))

Stage 3: where

| where UserIdentityAccountId != "anonymous"

Stage 4: where

| where EventSource startswith "s3."

Stage 5: where

| where EventName =~ "GetObject"

Stage 6: extend

| extend FilePath = tostring(parse_json(RequestParameters).key)

Stage 7: where

| where FilePath has_any(BEC_Keywords)

Stage 8: summarize

Events
| summarize dcount(FilePath) by UserIdentityPrincipalid, bin(startofday(TimeGenerated), 1d)

Stage 9: summarize

| summarize CountOfDocs = make_list(dcount_FilePath, 10000), TimeStamp = make_list(TimeGenerated, 10000) by UserIdentityPrincipalid

The stages below score time-series anomalies (make-series, series_decompose_anomalies).

Stage 10: extend

| extend (Anomalies, Score, Baseline) = series_decompose_anomalies(CountOfDocs, sensitivity, -1, 'linefit')

Stage 11: mv-expand

| mv-expand CountOfDocs to typeof(double), TimeStamp to typeof(datetime), Anomalies to typeof(double),Score to typeof(double), Baseline to typeof(long)

Stage 12: where

| where Anomalies > 0

Stage 13: project

| project TimeStamp, CountOfDocs, Baseline, Score, Anomalies, UserIdentityPrincipalid

Stage 14: join

| join kind=inner(Events | extend TimeStamp = startofday(TimeGenerated)) on TimeStamp, UserIdentityPrincipalid

Stage 15: extend

| extend Name = iif(UserIdentityUserName contains "@", split(UserIdentityUserName, "@")[0], UserIdentityUserName)

Stage 16: extend

| extend UPNSuffix = iif(UserIdentityUserName contains "@", split(UserIdentityUserName, "@")[1], "")

Stage 17: project-reorder

| project-reorder TimeGenerated, UserIdentityType, UserIdentityPrincipalid, UserIdentityUserName, FilePath, EventName, UserAgent, SourceIpAddress, CountOfDocs, Baseline, Score

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Anomaliesgt
  • 0
field:"Anomalies" kind:gt value:"0"
EventNameeq
  • GetObject
field:"aws::eventName" kind:eq value:"GetObject"
EventSourcestarts_with
  • s3.
field:"aws::eventSource" kind:starts_with value:"s3."
FilePathmatch
  • Bankuberweisung transforms: term
  • PO# transforms: term
  • Paiement transforms: term
  • Rechnung transforms: term
  • Zahlung transforms: term
  • account details transforms: term
  • bank account transforms: term
  • bank details transforms: term
  • bank statement transforms: term
  • closing transforms: term
  • deposit transforms: term
  • funds transforms: term
  • hacked transforms: term
  • invoice transforms: term
  • paycheck transforms: term
  • payment transforms: term
  • phishing transforms: term
  • purchase transforms: term
  • remittance transforms: term
  • transfer transforms: term
  • virement bancaire transforms: term
field:"FilePath" kind:match
UserIdentityAccountIdne
  • anonymous
field:"UserIdentityAccountId" kind:ne value:"anonymous"

Output fields

These fields are emitted when the rule matches.

FieldSource
Anomaliesproject
Baselineproject
CountOfDocsproject
Scoreproject
TimeStampproject
UserIdentityPrincipalidproject
Nameextend
UPNSuffixextend