id: 10f83299-c4e6-4af8-9627-5f9448dee24a
name: TI Map File Entity to Security Event
description: |
  'This query finds matches in Security Event data for known FileName Indicators of Compromise from Threat Intelligence sources. FileName matches may produce false positives, so use this for hunting rather than real-time detection.'
description-detailed: |
  'This query identifies any matches in the Security Event data that correspond to any known FileName Indicators of Compromise (IOC) from Threat Intelligence (TI) sources. 
  Since file name matches may produce a significant amount of false positives, it is recommended to use this query for hunting purposes rather than for real-time detection.'
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: ThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: ThreatIntelligenceTaxii
    dataTypes:
      - ThreatIntelligenceIndicator
  - connectorId: MicrosoftDefenderThreatIntelligence
    dataTypes:
      - ThreatIntelligenceIndicator
tactics:
  - Impact
query: |
  let starttime = todatetime('{{StartTimeISO}}');
  let endtime = todatetime('{{EndTimeISO}}');
  let ioc_lookBack = 14d;
  ThreatIntelIndicators
  | where TimeGenerated >= ago(ioc_lookBack) and ValidUntil > now()
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id
  | where IsActive == true
  //extract key part of kv pair
  | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
  | where IndicatorType == "file"
  | extend FileName = ObservableValue
  | where isnotempty(FileName)
  | extend _lowerFileName = tolower(FileName)
  // 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 (
    SecurityEvent 
    | where TimeGenerated between(starttime..endtime)
    | where EventID in ("4688","8002","4648","4673")
    | where isnotempty(Process)
    | extend SecurityEvent_TimeGenerated = TimeGenerated, Event = EventID, _lowerProcess = tolower(Process)
  )
  on $left._lowerFileName == $right._lowerProcess
  | where SecurityEvent_TimeGenerated < ValidUntil
  | summarize SecurityEvent_TimeGenerated = arg_max(SecurityEvent_TimeGenerated, *) by Id, Process
  | extend Description = tostring(parse_json(Data).description)
  | extend ActivityGroupNames = extract(@"ActivityGroup:(\S+)", 1, tostring(parse_json(Data).labels))
  | project SecurityEvent_TimeGenerated, Description, ActivityGroupNames, Id, Type, ValidUntil, Confidence,
  FileName, Computer, IpAddress, Account, Event, Activity//, Url
  | extend timestamp = SecurityEvent_TimeGenerated, NTDomain = split(Account, '\\', 0)[0], Name = split(Account, '\\', 1)[0], HostName = split(Computer, '.', 0)[0], DnsDomain = strcat_array(array_slice(split(Computer, '.'), 1, -1), '.')
  | extend Account_0_Name = Name
  | extend Account_0_NTDomain = NTDomain
  | extend Host_0_HostName = HostName
  | extend Host_0_DnsDomain = DnsDomain
  | extend IP_0_Address = IpAddress
  //| extend URL_0_Url = Url
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Name
      - identifier: NTDomain
        columnName: NTDomain 
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpAddress
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
version: 1.0.3

