Back
Id9c2cef4e-c3a3-4be9-8923-a2f820d4face
RulenamePRODAFT USTA - TI map File Hash to CommonSecurityLog
DescriptionIdentifies a match in CommonSecurityLog data from any file-hash indicator ingested from

PRODAFT USTA IoC Threat Intelligence (SourceSystem starting with “PRODAFT USTA”).
SeverityMedium
TacticsCommandAndControl
TechniquesT1071
Required data connectorsCefAma
PRODAFTUstaIoCUploadIndicators
KindScheduled
Query frequency1h
Query period14d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Solutions/PRODAFT%20USTA%20-%20IoC%20Threat%20Intelligence/Analytic%20Rules/TIMapFileHashToCommonSecurityLog.yaml
Version1.0.0
Arm template9c2cef4e-c3a3-4be9-8923-a2f820d4face.json
Deploy To Azure
let dt_lookBack = 1h;
let ioc_lookBack = 14d;
let fileHashIndicators = ThreatIntelIndicators
// Each USTA IoC feed uploads under its own SourceSystem ("PRODAFT USTA - Malware Hashes", etc.)
| where SourceSystem startswith "PRODAFT USTA"
// ObservableKey is a STIX kv pair ("file:hashes.'SHA-256'") - take the type before the colon
| extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
| where IndicatorType == "file"
| extend FileHashType = extract(@"hashes\.'([^']+)'", 1, ObservableKey)
| where isnotempty(FileHashType)
| extend FileHashValue = toupper(ObservableValue)
| extend IndicatorId = tostring(split(Id, "--")[2])
| extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
| where TimeGenerated >= ago(ioc_lookBack)
| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
| where IsActive and (ValidUntil > now() or isempty(ValidUntil));
// CommonSecurityLog hash casing varies by source, so match both forms
(fileHashIndicators | extend  FileHashValue = tolower(FileHashValue)
| union (fileHashIndicators | extend FileHashValue = toupper(FileHashValue)))
| project-reorder *, FileHashType, FileHashValue, Type
// innerunique: one match per indicator is enough to alert, and it keeps the join cheap
|  join kind=innerunique (
  CommonSecurityLog | where TimeGenerated >= ago(dt_lookBack)
  | extend CommonSecurityLog_TimeGenerated = TimeGenerated
  )
on $left.FileHashValue == $right.FileHash
// non-expiring indicators are allowed above, so they must survive this filter too
| where isempty(ValidUntil) or CommonSecurityLog_TimeGenerated < ValidUntil
| summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, FileHashValue
| extend Description = tostring(parse_json(Data).description)
| extend IndicatorTags = tostring(parse_json(Data).labels)
| project CommonSecurityLog_TimeGenerated, Description, IndicatorTags, Id, ValidUntil, Confidence,
SourceIP, SourcePort, DestinationIP, DestinationPort, SourceUserID, SourceUserName, DeviceName, DeviceAction,
RequestURL, DestinationUserName, DestinationUserID, ApplicationProtocol, Activity, FileHashValue, FileHashType, Url
| extend HostName = tostring(split(DeviceName, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(DeviceName, '.'), 1, -1), '.'))
| extend Name = tostring(split(SourceUserName, '@', 0)[0]), UPNSuffix = tostring(split(SourceUserName, '@', 1)[0])
| extend timestamp = CommonSecurityLog_TimeGenerated
name: PRODAFT USTA - TI map File Hash to CommonSecurityLog
triggerOperator: gt
query: |
  let dt_lookBack = 1h;
  let ioc_lookBack = 14d;
  let fileHashIndicators = ThreatIntelIndicators
  // Each USTA IoC feed uploads under its own SourceSystem ("PRODAFT USTA - Malware Hashes", etc.)
  | where SourceSystem startswith "PRODAFT USTA"
  // ObservableKey is a STIX kv pair ("file:hashes.'SHA-256'") - take the type before the colon
  | extend IndicatorType = replace(@"\[|\]|\""", "", tostring(split(ObservableKey, ":", 0)))
  | where IndicatorType == "file"
  | extend FileHashType = extract(@"hashes\.'([^']+)'", 1, ObservableKey)
  | where isnotempty(FileHashType)
  | extend FileHashValue = toupper(ObservableValue)
  | extend IndicatorId = tostring(split(Id, "--")[2])
  | extend Url = iff(ObservableKey == "url:value", ObservableValue, "")
  | where TimeGenerated >= ago(ioc_lookBack)
  | summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue
  | where IsActive and (ValidUntil > now() or isempty(ValidUntil));
  // CommonSecurityLog hash casing varies by source, so match both forms
  (fileHashIndicators | extend  FileHashValue = tolower(FileHashValue)
  | union (fileHashIndicators | extend FileHashValue = toupper(FileHashValue)))
  | project-reorder *, FileHashType, FileHashValue, Type
  // innerunique: one match per indicator is enough to alert, and it keeps the join cheap
  |  join kind=innerunique (
    CommonSecurityLog | where TimeGenerated >= ago(dt_lookBack)
    | extend CommonSecurityLog_TimeGenerated = TimeGenerated
    )
  on $left.FileHashValue == $right.FileHash
  // non-expiring indicators are allowed above, so they must survive this filter too
  | where isempty(ValidUntil) or CommonSecurityLog_TimeGenerated < ValidUntil
  | summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, FileHashValue
  | extend Description = tostring(parse_json(Data).description)
  | extend IndicatorTags = tostring(parse_json(Data).labels)
  | project CommonSecurityLog_TimeGenerated, Description, IndicatorTags, Id, ValidUntil, Confidence,
  SourceIP, SourcePort, DestinationIP, DestinationPort, SourceUserID, SourceUserName, DeviceName, DeviceAction,
  RequestURL, DestinationUserName, DestinationUserID, ApplicationProtocol, Activity, FileHashValue, FileHashType, Url
  | extend HostName = tostring(split(DeviceName, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(DeviceName, '.'), 1, -1), '.'))
  | extend Name = tostring(split(SourceUserName, '@', 0)[0]), UPNSuffix = tostring(split(SourceUserName, '@', 1)[0])
  | extend timestamp = CommonSecurityLog_TimeGenerated
queryFrequency: 1h
description: |
  'Identifies a match in CommonSecurityLog data from any file-hash indicator ingested from
  PRODAFT USTA IoC Threat Intelligence (SourceSystem starting with "PRODAFT USTA").'
id: 9c2cef4e-c3a3-4be9-8923-a2f820d4face
triggerThreshold: 0
queryPeriod: 14d
version: 1.0.0
kind: Scheduled
relevantTechniques:
- T1071
severity: Medium
requiredDataConnectors:
- connectorId: CefAma
  dataTypes:
  - CommonSecurityLog
- connectorId: PRODAFTUstaIoCUploadIndicators
  dataTypes:
  - ThreatIntelIndicators
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/PRODAFT%20USTA%20-%20IoC%20Threat%20Intelligence/Analytic%20Rules/TIMapFileHashToCommonSecurityLog.yaml
tactics:
- CommandAndControl
entityMappings:
- fieldMappings:
  - identifier: FullName
    columnName: SourceUserName
  - identifier: Name
    columnName: Name
  - identifier: UPNSuffix
    columnName: UPNSuffix
  entityType: Account
- fieldMappings:
  - identifier: FullName
    columnName: DeviceName
  - identifier: HostName
    columnName: HostName
  - identifier: DnsDomain
    columnName: DnsDomain
  entityType: Host
- fieldMappings:
  - identifier: Address
    columnName: SourceIP
  entityType: IP
- fieldMappings:
  - identifier: Url
    columnName: Url
  entityType: URL
- fieldMappings:
  - identifier: Value
    columnName: FileHashValue
  - identifier: Algorithm
    columnName: FileHashType
  entityType: FileHash
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2024-01-01-preview",
      "id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/9c2cef4e-c3a3-4be9-8923-a2f820d4face')]",
      "kind": "Scheduled",
      "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/9c2cef4e-c3a3-4be9-8923-a2f820d4face')]",
      "properties": {
        "alertRuleTemplateName": "9c2cef4e-c3a3-4be9-8923-a2f820d4face",
        "customDetails": null,
        "description": "'Identifies a match in CommonSecurityLog data from any file-hash indicator ingested from\nPRODAFT USTA IoC Threat Intelligence (SourceSystem starting with \"PRODAFT USTA\").'\n",
        "displayName": "PRODAFT USTA - TI map File Hash to CommonSecurityLog",
        "enabled": true,
        "entityMappings": [
          {
            "entityType": "Account",
            "fieldMappings": [
              {
                "columnName": "SourceUserName",
                "identifier": "FullName"
              },
              {
                "columnName": "Name",
                "identifier": "Name"
              },
              {
                "columnName": "UPNSuffix",
                "identifier": "UPNSuffix"
              }
            ]
          },
          {
            "entityType": "Host",
            "fieldMappings": [
              {
                "columnName": "DeviceName",
                "identifier": "FullName"
              },
              {
                "columnName": "HostName",
                "identifier": "HostName"
              },
              {
                "columnName": "DnsDomain",
                "identifier": "DnsDomain"
              }
            ]
          },
          {
            "entityType": "IP",
            "fieldMappings": [
              {
                "columnName": "SourceIP",
                "identifier": "Address"
              }
            ]
          },
          {
            "entityType": "URL",
            "fieldMappings": [
              {
                "columnName": "Url",
                "identifier": "Url"
              }
            ]
          },
          {
            "entityType": "FileHash",
            "fieldMappings": [
              {
                "columnName": "FileHashValue",
                "identifier": "Value"
              },
              {
                "columnName": "FileHashType",
                "identifier": "Algorithm"
              }
            ]
          }
        ],
        "OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Solutions/PRODAFT%20USTA%20-%20IoC%20Threat%20Intelligence/Analytic%20Rules/TIMapFileHashToCommonSecurityLog.yaml",
        "query": "let dt_lookBack = 1h;\nlet ioc_lookBack = 14d;\nlet fileHashIndicators = ThreatIntelIndicators\n// Each USTA IoC feed uploads under its own SourceSystem (\"PRODAFT USTA - Malware Hashes\", etc.)\n| where SourceSystem startswith \"PRODAFT USTA\"\n// ObservableKey is a STIX kv pair (\"file:hashes.'SHA-256'\") - take the type before the colon\n| extend IndicatorType = replace(@\"\\[|\\]|\\\"\"\", \"\", tostring(split(ObservableKey, \":\", 0)))\n| where IndicatorType == \"file\"\n| extend FileHashType = extract(@\"hashes\\.'([^']+)'\", 1, ObservableKey)\n| where isnotempty(FileHashType)\n| extend FileHashValue = toupper(ObservableValue)\n| extend IndicatorId = tostring(split(Id, \"--\")[2])\n| extend Url = iff(ObservableKey == \"url:value\", ObservableValue, \"\")\n| where TimeGenerated >= ago(ioc_lookBack)\n| summarize LatestIndicatorTime = arg_max(TimeGenerated, *) by Id, ObservableValue\n| where IsActive and (ValidUntil > now() or isempty(ValidUntil));\n// CommonSecurityLog hash casing varies by source, so match both forms\n(fileHashIndicators | extend  FileHashValue = tolower(FileHashValue)\n| union (fileHashIndicators | extend FileHashValue = toupper(FileHashValue)))\n| project-reorder *, FileHashType, FileHashValue, Type\n// innerunique: one match per indicator is enough to alert, and it keeps the join cheap\n|  join kind=innerunique (\n  CommonSecurityLog | where TimeGenerated >= ago(dt_lookBack)\n  | extend CommonSecurityLog_TimeGenerated = TimeGenerated\n  )\non $left.FileHashValue == $right.FileHash\n// non-expiring indicators are allowed above, so they must survive this filter too\n| where isempty(ValidUntil) or CommonSecurityLog_TimeGenerated < ValidUntil\n| summarize CommonSecurityLog_TimeGenerated = arg_max(CommonSecurityLog_TimeGenerated, *) by IndicatorId, FileHashValue\n| extend Description = tostring(parse_json(Data).description)\n| extend IndicatorTags = tostring(parse_json(Data).labels)\n| project CommonSecurityLog_TimeGenerated, Description, IndicatorTags, Id, ValidUntil, Confidence,\nSourceIP, SourcePort, DestinationIP, DestinationPort, SourceUserID, SourceUserName, DeviceName, DeviceAction,\nRequestURL, DestinationUserName, DestinationUserID, ApplicationProtocol, Activity, FileHashValue, FileHashType, Url\n| extend HostName = tostring(split(DeviceName, '.', 0)[0]), DnsDomain = tostring(strcat_array(array_slice(split(DeviceName, '.'), 1, -1), '.'))\n| extend Name = tostring(split(SourceUserName, '@', 0)[0]), UPNSuffix = tostring(split(SourceUserName, '@', 1)[0])\n| extend timestamp = CommonSecurityLog_TimeGenerated\n",
        "queryFrequency": "PT1H",
        "queryPeriod": "P14D",
        "severity": "Medium",
        "subTechniques": [],
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "CommandAndControl"
        ],
        "techniques": [
          "T1071"
        ],
        "templateVersion": "1.0.0",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}