Detection rules › Kusto

Microsoft COVID-19 file hash indicator matches

Status
available
Severity
medium
Time window
14d
Group by
FileHash, FileHashValue
Source
github.com/Azure/Azure-Sentinel

'Identifies a match in CommonSecurityLog Event data from any FileHash published in the Microsoft COVID-19 Threat Intel Feed - as described at https://www.microsoft.com/security/blog/2020/05/14/open-sourcing-covid-threat-intelligence/'

MITRE ATT&CK coverage

Rule body kusto

id: 2be4ef67-a93f-4d8a-981a-88158cb73abd
name: Microsoft COVID-19 file hash indicator matches
description: |
  'Identifies a match in CommonSecurityLog Event data from any FileHash published in the Microsoft COVID-19 Threat Intel Feed - as described at https://www.microsoft.com/security/blog/2020/05/14/open-sourcing-covid-threat-intelligence/'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: CefAma
    dataTypes:
      - CommonSecurityLog
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1204.002
query: |
  let dt_lookBack = 1h;
  let covidIndicators = (externaldata(TimeGenerated:datetime, FileHashValue:string, FileHashType: string, TlpLevel: string, Product: string, ThreatType: string, Description: string )
  [@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/Microsoft.Covid19.Indicators.csv"] with (format="csv"));
  let fileHashIndicators = covidIndicators
  | where isnotempty(FileHashValue);
  // Handle matches against both lower case and uppercase versions of the hash:
  (fileHashIndicators | extend FileHashValue = tolower(FileHashValue)
  | union (fileHashIndicators | extend FileHashValue = toupper(FileHashValue)))
  // using innerunique to keep perf fast and result set low, we only need one match to indicate potential malicious activity that needs to be investigated
  |  join kind=innerunique (
     CommonSecurityLog | where TimeGenerated >= ago(dt_lookBack)
     | where isnotempty(FileHash)
     | extend CommonSecurityLog_TimeGenerated = TimeGenerated
     )
  on $left.FileHashValue == $right.FileHash
  | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by FileHashValue
  | project CommonSecurityLog_TimeGenerated, FileHashValue, FileHashType, Description, ThreatType,
  SourceIP, SourcePort, DestinationIP, DestinationPort, SourceUserID, SourceUserName, DeviceName, DeviceAction,
  RequestURL, DestinationUserName, DestinationUserID, ApplicationProtocol, Activity
  | extend AccountName = tostring(split(SourceUserName, "@")[0]), AccountUPNSuffix = tostring(split(SourceUserName, "@")[1])
  | extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: SourceUserName
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: AccountUPNSuffix
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: FileHash
    fieldMappings:
      - identifier: Value
        columnName: FileHashValue
      - identifier: Algorithm
        columnName: FileHashType
version: 1.3.6
kind: Scheduled

Stages and Predicates

Parameters

let dt_lookBack = 1h;

Let binding: covidIndicators

let covidIndicators = (externaldata(TimeGenerated:datetime, FileHashValue:string, FileHashType: string, TlpLevel: string, Product: string, ThreatType: string, Description: string )
[@"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/Microsoft.Covid19.Indicators.csv"] with (format="csv"));

The stages below define let fileHashIndicators (the rule's main pipeline source).

Stage 1: source

covidIndicators

Stage 2: where

| where isnotempty(FileHashValue)

The stages below run on fileHashIndicators (the outer pipeline).

Stage 3: extend

fileHashIndicators
| extend FileHashValue = tolower(FileHashValue)

Stage 4: union

| union

Stage 5: source

covidIndicators

Stage 6: where

| where isnotempty(FileHashValue)

Stage 7: extend

| extend FileHashValue = toupper(FileHashValue)

Stage 8: join

| join kind=innerunique (
   CommonSecurityLog | where TimeGenerated >= ago(dt_lookBack)
   | where isnotempty(FileHash)
   | extend CommonSecurityLog_TimeGenerated = TimeGenerated
   )
on $left.FileHashValue == $right.FileHash

Stage 9: summarize

| summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by FileHashValue

Stage 10: project

| project CommonSecurityLog_TimeGenerated, FileHashValue, FileHashType, Description, ThreatType,
SourceIP, SourcePort, DestinationIP, DestinationPort, SourceUserID, SourceUserName, DeviceName, DeviceAction,
RequestURL, DestinationUserName, DestinationUserID, ApplicationProtocol, Activity

Stage 11: extend (3 consecutive steps)

| extend AccountName = tostring(split(SourceUserName, "@")[0]), AccountUPNSuffix = tostring(split(SourceUserName, "@")[1])
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)

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
FileHashis_not_null
  • (no value, null check)
FileHashValueis_not_null
  • (no value, null check)

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
Activityproject
ApplicationProtocolproject
CommonSecurityLog_TimeGeneratedproject
Descriptionproject
DestinationIPproject
DestinationPortproject
DestinationUserIDproject
DestinationUserNameproject
DeviceActionproject
DeviceNameproject
FileHashTypeproject
FileHashValueproject
RequestURLproject
SourceIPproject
SourcePortproject
SourceUserIDproject
SourceUserNameproject
ThreatTypeproject
AccountNameextend
AccountUPNSuffixextend
DomainIndexextend
HostNameextend
HostNameDomainextend