Detection rules › Kusto

Mass Download & copy to USB device by single user

Severity
medium
Time window
1d
Group by
AADUserId, Alert_TimeGenerated, CloudAppEvents_Details, DeviceFileEvents_Details, DeviceId, DeviceName, IPAddress, InitiatingProcessAccountUpn, USB_Details, USB_TimeGenerated, UserId
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

This query looks for any mass download by a single user with possible file copy activity to a new USB drive. Malicious insiders may perform such activities that may cause harm to the organization. This query could also reveal unintentional insider that had no intention of malicious activity but their actions may impact an organizations security posture. Reference:https://docs.microsoft.com/defender-cloud-apps/policy-template-reference

MITRE ATT&CK coverage

TacticTechniques
Exfiltration

Telemetry coverage

Rule body

id: 6267ce44-1e9d-471b-9f1e-ae76a6b7aa84
name: Mass Download & copy to USB device by single user
description: |
  'This query looks for any mass download by a single user with possible file copy activity to a new USB drive. Malicious insiders may perform such activities that may cause harm to the organization. 
  This query could also reveal unintentional insider that had no intention of malicious activity but their actions may impact an organizations security posture.
  Reference:https://docs.microsoft.com/defender-cloud-apps/policy-template-reference'
severity: Medium
requiredDataConnectors:
  - connectorId: MicrosoftCloudAppSecurity
    dataTypes:
      - SecurityAlert
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - CloudAppEvents
      - DeviceEvents
      - DeviceFileEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1052
query: |
  let Alerts = SecurityAlert
  | where AlertName =~ "mass download by a single user"
  | where Status != 'Resolved'
  | extend ipEnt = parse_json(Entities), accountEnt = parse_json(Entities)
  | mv-apply tempParams = ipEnt on (
  mv-expand ipEnt
  | where ipEnt.Type == "ip" 
  | extend IpAddress = tostring(ipEnt.Address)
  )
  | mv-apply tempParams = accountEnt on (
  mv-expand accountEnt
  | where accountEnt.Type == "account"
  | extend AADUserId = tostring(accountEnt.AadUserId)
  )
  | extend Alert_TimeGenerated = TimeGenerated
  | distinct Alert_TimeGenerated, IpAddress, AADUserId, DisplayName, Description, ProductName, ExtendedProperties, Entities, Status, CompromisedEntity
  ;
  let CA_Events = CloudAppEvents
  | where ActionType == "FileDownloaded"
  | extend parsed = parse_json(RawEventData)
  | extend UserId = tostring(parsed.UserId)
  | extend FileName = tostring(parsed.SourceFileName)
  | extend FileExtension = tostring(parsed.SourceFileExtension)
  | summarize CloudAppEvent_StartTime = min(TimeGenerated), CloudAppEvent_EndTime = max(TimeGenerated), CloudAppEvent_Files = make_set(FileName), FileCount = dcount(FileName) by Application, AccountObjectId, UserId, IPAddress, City, CountryCode
  | extend CloudAppEvents_Details = pack_all();
  let CA_Alerts_Events = Alerts | join kind=inner (CA_Events)
  on $left.AADUserId == $right.AccountObjectId and $left.IpAddress == $right.IPAddress
  // Cloud app event comes before Alert
  | where CloudAppEvent_EndTime <= Alert_TimeGenerated
  | project Alert_TimeGenerated, UserId, AADUserId, IPAddress, CloudAppEvents_Details, CloudAppEvent_Files
  ;
  // setup list to filter DeviceFileEvents for only files downloaded as indicated by CloudAppEvents
  let CA_FileList = CA_Alerts_Events | project CloudAppEvent_Files;
  CA_Alerts_Events
  | join kind=inner ( DeviceFileEvents
  | where ActionType in ("FileCreated", "FileRenamed")
  | where FileName in~ (CA_FileList)
  | summarize DeviceFileEvent_StartTime = min(TimeGenerated), DeviceFileEvent_EndTime = max(TimeGenerated), DeviceFileEvent_Files = make_set(FolderPath), DeviceFileEvent_FileCount = dcount(FolderPath) by InitiatingProcessAccountUpn, DeviceId, DeviceName, InitiatingProcessFolderPath, InitiatingProcessParentFileName//, InitiatingProcessCommandLine
  | extend DeviceFileEvents_Details = pack_all()
  ) on $left.UserId == $right.InitiatingProcessAccountUpn
  | where DeviceFileEvent_StartTime >= Alert_TimeGenerated
  | join kind=inner (
  // get device events where a USB drive was mounted
  DeviceEvents
  | where ActionType == "UsbDriveMounted"
  | extend parsed = parse_json(AdditionalFields)
  | extend USB_DriveLetter = tostring(AdditionalFields.DriveLetter), USB_ProductName = tostring(AdditionalFields.ProductName), USB_Volume = tostring(AdditionalFields.Volume)
  | where isnotempty(USB_DriveLetter)
  | project USB_TimeGenerated = TimeGenerated, DeviceId, USB_DriveLetter, USB_ProductName, USB_Volume
  | extend USB_Details = pack_all()
  )  
  on DeviceId
  // USB event occurs after the Alert
  | where USB_TimeGenerated >= Alert_TimeGenerated
  | mv-expand DeviceFileEvent_Files
  | extend DeviceFileEvent_Files = tostring(DeviceFileEvent_Files)
  // make sure that we only pickup the files that have the USB drive letter
  | where DeviceFileEvent_Files startswith USB_DriveLetter
  | summarize USB_Drive_MatchedFiles = make_set_if(DeviceFileEvent_Files, DeviceFileEvent_Files startswith USB_DriveLetter) by Alert_TimeGenerated, USB_TimeGenerated, UserId, AADUserId, DeviceId, DeviceName, IPAddress, CloudAppEvents_Details = tostring(CloudAppEvents_Details), DeviceFileEvents_Details = tostring(DeviceFileEvents_Details), USB_Details = tostring(USB_Details)
  | extend InitiatingProcessFileName = tostring(split(todynamic(DeviceFileEvents_Details).InitiatingProcessFolderPath, "\\")[-1]), InitiatingProcessFolderPath = tostring(todynamic(DeviceFileEvents_Details).InitiatingProcessFolderPath)
  | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
  | extend HostNameDomain = iff(DeviceName != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
  | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
  | project-away DomainIndex
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: UserId
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: AadUserId
        columnName: AADUserId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPAddress
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: File
    fieldMappings:
      - identifier: Name
        columnName: InitiatingProcessFileName
      - identifier: Directory
        columnName: InitiatingProcessFolderPath
version: 1.0.4
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

let Alerts and let CA_Alerts_Events are inlined into the numbered stages below.

Let binding: CA_Events used in Stages 1, 12

let CA_Events = CloudAppEvents
| where ActionType == "FileDownloaded"
| extend parsed = parse_json(RawEventData)
| extend UserId = tostring(parsed.UserId)
| extend FileName = tostring(parsed.SourceFileName)
| extend FileExtension = tostring(parsed.SourceFileExtension)
| summarize CloudAppEvent_StartTime = min(TimeGenerated), CloudAppEvent_EndTime = max(TimeGenerated), CloudAppEvent_Files = make_set(FileName), FileCount = dcount(FileName) by Application, AccountObjectId, UserId, IPAddress, City, CountryCode
| extend CloudAppEvents_Details = pack_all();

Let binding: CA_FileList used in Stage 3

let CA_FileList = CA_Alerts_Events | project CloudAppEvent_Files;

Stage 1: source

let CA_Events

Stage 2: source

let CA_Alerts_Events

Stage 3: source

let CA_FileList

Stage 4: source

SecurityAlert

Stage 5: where

where AlertName =~ "mass download by a single user"

Stage 6: where

where Status !~ "Resolved"

Stage 7: extend

extend accountEnt, ipEnt

Stage 8: kusto:mv-apply

kusto:mv-apply Type =~ "ip"

Stage 9: kusto:mv-apply

kusto:mv-apply Type =~ "account"

Stage 10: extend

extend Alert_TimeGenerated

Stage 11: distinct

distinct AADUserId, Alert_TimeGenerated, CompromisedEntity, Description, DisplayName, Entities, ExtendedProperties, IpAddress, ProductName, Status

Stage 12: join

join kind=inner (CA_Events)

Stage 13: where

where CloudAppEvent_EndTime <= Alert_TimeGenerated

Stage 14: project

project AADUserId, Alert_TimeGenerated, CloudAppEvent_Files, CloudAppEvents_Details, IPAddress, UserId

Stage 15: join

join kind=inner (DeviceFileEvents) on UserId, InitiatingProcessAccountUpn

Stage 16: where

where DeviceFileEvent_StartTime >= Alert_TimeGenerated

Stage 17: join

join kind=inner (DeviceEvents) on DeviceId

Stage 18: where

where USB_TimeGenerated >= Alert_TimeGenerated

Stage 19: mv-expand

mv-expand DeviceFileEvent_Files

Stage 20: extend

extend DeviceFileEvent_Files

Stage 21: where

where DeviceFileEvent_Files startswith "USB_DriveLetter"

Stage 22: summarize

summarize CloudAppEvents_Details, DeviceFileEvents_Details, USB_Details, USB_Drive_MatchedFiles by Alert_TimeGenerated, USB_TimeGenerated, UserId, AADUserId, DeviceId, DeviceName, IPAddress, CloudAppEvents_Details, DeviceFileEvents_Details, USB_Details

Stage 23: extend (4 consecutive steps)

extend AccountName, AccountUPNSuffix, DomainIndex, HostName, HostNameDomain, InitiatingProcessFileName, InitiatingProcessFolderPath

Stage 24: project-away

project-away DomainIndex

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
AADUserIdsummarize
Alert_TimeGeneratedsummarize
CloudAppEvents_Detailssummarize
DeviceFileEvents_Detailssummarize
DeviceIdsummarize
DeviceNamesummarize
IPAddresssummarize
USB_Detailssummarize
USB_Drive_MatchedFilessummarize
USB_TimeGeneratedsummarize
UserIdsummarize
InitiatingProcessFileNameextend
InitiatingProcessFolderPathextend
HostNameextend
HostNameDomainextend
AccountNameextend
AccountUPNSuffixextend