Detection rules › Kusto

Azure WAF matching for Log4j vuln(CVE-2021-44228)

Status
available
Severity
high
Time window
6h
Source
github.com/Azure/Azure-Sentinel

'This query will alert on a positive pattern match by Azure WAF for CVE-2021-44228 log4j vulnerability exploitation attempt. If possible, it then decodes the malicious command for further analysis. Reference: https://www.microsoft.com/security/blog/2021/12/11/guidance-for-preventing-detecting-and-hunting-for-cve-2021-44228-log4j-2-exploitation/'

MITRE ATT&CK coverage

TacticTechniques
Initial AccessT1190 Exploit Public-Facing Application

Rule body kusto

id: 2de8abd6-a613-450e-95ed-08e503369fb3
name: Azure WAF matching for Log4j vuln(CVE-2021-44228)
description: |
  'This query will alert on a positive pattern match by Azure WAF for CVE-2021-44228 log4j vulnerability exploitation attempt. If possible, it then decodes the malicious command for further analysis.
   Reference: https://www.microsoft.com/security/blog/2021/12/11/guidance-for-preventing-detecting-and-hunting-for-cve-2021-44228-log4j-2-exploitation/'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: WAF
    dataTypes:
      - AzureDiagnostics
queryFrequency: 6h
queryPeriod: 6h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
tags:
  - CVE-2021-44228
  - log4j 
  - log4shell
query: |
  let log4jioc = dynamic(["jndi","ldap","${::"]);
  AzureDiagnostics
  | where ResourceProvider == "MICROSOFT.NETWORK" and Category in ("ApplicationGatewayFirewallLog", "FrontdoorWebApplicationFirewallLog")
  | extend details_data_s = column_ifexists("details_data_s", tostring(AdditionalFields.details_data))
  |where requestUri_s has_any (log4jioc) or details_message_s has_any (log4jioc) or details_data_s has_any (log4jioc)
  | extend Malicious = iff(isnotempty( details_data_s),details_data_s,iff(isnotempty( requestUri_s),requestUri_s,""))
  |parse Malicious with * '${' MaliciousCommand '}' * 
  | extend EncodeCmd = iff(MaliciousCommand has 'Base64/', split(split(MaliciousCommand, "Base64/",1)[0], "}", 0)[0], "")
  | extend EncodeCmd1 = iff(MaliciousCommand has 'base64/', split(split(MaliciousCommand, "base64/",1)[0], "}", 0)[0], "")
  | extend CmdLine = iff( isnotempty(EncodeCmd), EncodeCmd, EncodeCmd1)
  | extend DecodedCmdLine = base64_decode_tostring(tostring(CmdLine))
  | extend DecodedCmdLine = iff( isnotempty(DecodedCmdLine), DecodedCmdLine, "Unable to decode/Doesn't need decoding")
  | project TimeGenerated, Target=column_ifexists("hostname_s", tostring(AdditionalFields.hostname)), MaliciousHost = column_ifexists("clientIp_s", tostring(AdditionalFields.clientIp)) , MaliciousCommand, details_data_s = column_ifexists("details_data_s", tostring(AdditionalFields.details_data)), DecodedCmdLine, Message,
  ruleSetType_s = column_ifexists("ruleSetType_s", tostring(AdditionalFields.ruleSetType)), OperationName, SubscriptionId, details_message_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_message)), 
  details_file_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_file))
  | extend timestamp = TimeGenerated
entityMappings: 
- entityType: IP
  fieldMappings:
    - identifier: Address
      columnName: MaliciousHost
version: 1.0.4
kind: Scheduled

Stages and Predicates

Parameters

let log4jioc = dynamic(["jndi","ldap","${::"]);

Stage 1: source

AzureDiagnostics

Stage 2: where

| where ResourceProvider == "MICROSOFT.NETWORK" and Category in ("ApplicationGatewayFirewallLog", "FrontdoorWebApplicationFirewallLog")

Stage 3: extend

| extend details_data_s = column_ifexists("details_data_s", tostring(AdditionalFields.details_data))

Stage 4: where

| where requestUri_s has_any (log4jioc) or details_message_s has_any (log4jioc) or details_data_s has_any (log4jioc)

Stage 5: extend

| extend Malicious = iff(isnotempty( details_data_s),details_data_s,iff(isnotempty( requestUri_s),requestUri_s,""))
Malicious =
ifisnotempty(details_data_s)details_data_s
elseiff(isnotempty(requestUri_s), requestUri_s, "")

Stage 6: parse

| parse Malicious with * '${' MaliciousCommand '}' *

Stage 7: extend (5 consecutive steps)

| extend EncodeCmd = iff(MaliciousCommand has 'Base64/', split(split(MaliciousCommand, "Base64/",1)[0], "}", 0)[0], "")
| extend EncodeCmd1 = iff(MaliciousCommand has 'base64/', split(split(MaliciousCommand, "base64/",1)[0], "}", 0)[0], "")
| extend CmdLine = iff( isnotempty(EncodeCmd), EncodeCmd, EncodeCmd1)
| extend DecodedCmdLine = base64_decode_tostring(tostring(CmdLine))
| extend DecodedCmdLine = iff( isnotempty(DecodedCmdLine), DecodedCmdLine, "Unable to decode/Doesn't need decoding")
EncodeCmd =
ifMaliciousCommand has "Base64/"split(split(MaliciousCommand, "Base64/", 1)[0], "}", 0)[0]
else""

Stage 8: project

| project TimeGenerated, Target=column_ifexists("hostname_s", tostring(AdditionalFields.hostname)), MaliciousHost = column_ifexists("clientIp_s", tostring(AdditionalFields.clientIp)) , MaliciousCommand, details_data_s = column_ifexists("details_data_s", tostring(AdditionalFields.details_data)), DecodedCmdLine, Message,
ruleSetType_s = column_ifexists("ruleSetType_s", tostring(AdditionalFields.ruleSetType)), OperationName, SubscriptionId, details_message_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_message)), 
details_file_s = column_ifexists("details_message_s", tostring(AdditionalFields.details_file))

Stage 9: extend

| extend timestamp = TimeGenerated

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
Categoryin
  • ApplicationGatewayFirewallLog transforms: cased
  • FrontdoorWebApplicationFirewallLog transforms: cased
ResourceProvidereq
  • MICROSOFT.NETWORK transforms: cased
details_data_smatch
  • ${::
  • jndi
  • ldap
details_message_smatch
  • ${::
  • jndi
  • ldap
requestUri_smatch
  • ${::
  • jndi
  • ldap

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
DecodedCmdLineproject
MaliciousCommandproject
MaliciousHostproject
Messageproject
OperationNameproject
SubscriptionIdproject
Targetproject
TimeGeneratedproject
details_data_sproject
details_file_sproject
details_message_sproject
ruleSetType_sproject
timestampextend