Detection rules › Kusto

User agent search for log4j exploitation attempt

Status
available
Severity
high
Time window
1d
Group by
Account, AppDisplayName, ClientAppUsed, DstIpAddr, EventName, HttpStatus, HttpUserAgent, Operation, ResourceId, ResourceType, SourceIP, Type, Url, UserAgent, csMethod, ruleName_s, sSiteName
Source
github.com/Azure/Azure-Sentinel

'This query uses various log sources having user agent data to look for log4j CVE-2021-44228 exploitation attempt based on user agent pattern. Log4j is an open-source Apache logging library that is used in many Java-based applications. The regex and the string matching look for the most common attacks. This might not be comprehensive to detect every possible user agent variation. Reference: https://msrc-blog.microsoft.com/2021/12/11/microsofts-response-to-cve-2021-44228-apache-log4j2/'

MITRE ATT&CK coverage

TacticTechniques
Initial AccessT1190 Exploit Public-Facing Application

Rule body kusto

id: 29283b22-a1c0-4d16-b0a9-3460b655a46a
name: User agent search for log4j exploitation attempt
description: |
  'This query uses various log sources having user agent data to look for log4j CVE-2021-44228 exploitation attempt based on user agent pattern.
  Log4j is an open-source Apache logging library that is used in many Java-based applications. The regex and the string matching look for the most common attacks. This might not be comprehensive to detect every possible user agent variation.
   Reference: https://msrc-blog.microsoft.com/2021/12/11/microsofts-response-to-cve-2021-44228-apache-log4j2/'
severity: High
status: Available
requiredDataConnectors:
  - connectorId: SquidProxy
    dataTypes:
      - SquidProxy_CL
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog
  - connectorId: WAF
    dataTypes:
      - AzureDiagnostics
  - connectorId: Office365
    dataTypes:
      - OfficeActivity
  - connectorId: AzureActiveDirectory
    dataTypes:
      - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
  - connectorId: AWS
    dataTypes:
      - AWSCloudTrail
  - connectorId: AzureMonitor(IIS)
    dataTypes:
      - W3CIISLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
relevantTechniques:
  - T1190
tags:
    - log4j
    - log4shell
    - CVE2021-44228
    - Schema: ASimWebSession
    - SchemaVersion: 0.2.1
    - Schema: ASimNetworkSessions
    - SchemaVersion: 0.2.1
query: |
  let UserAgentString = dynamic (["${jndi:ldap:/", "${jndi:rmi:/", "${jndi:ldaps:/", "${jndi:dns:/", "${jndi:iiop:/","${jndi:","${jndi:nds:/","${jndi:corba/"]);
  let UARegexMinimalString=dynamic(['{','%7b', '%7B']);
  let UARegex = @'(\\$|%24)(\\{|%7B)([^jJ]*[jJ])([^nN]*[nN])([^dD]*[dD])([^iI]*[iI])(:|%3A|\\$|%24|}|%7D)';
  (union isfuzzy=true
  (OfficeActivity
  | where UserAgent has_any (UserAgentString) or UserAgent matches regex UARegex
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, Operation
  ),
  (AzureDiagnostics
  | where Category in ("FrontdoorWebApplicationFirewallLog", "FrontdoorAccessLog", "ApplicationGatewayFirewallLog", "ApplicationGatewayAccessLog")
  | where userAgent_s has_any (UserAgentString) or userAgent_s matches regex UARegex
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = userAgent_s, SourceIP = column_ifexists("clientIp_s",clientIP_s), Type, column_ifexists("originalHost_s",host_s), Url = requestUri_s, HttpStatus = column_ifexists("httpStatusDetails_s",httpStatus_d), column_ifexists("trackingReference_s",transactionId_g), ruleName_s, ResourceType, ResourceId
  ),
  (
  W3CIISLog
  | where csUserAgent has_any (UserAgentString) or csUserAgent matches regex UARegex
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, Url = csUriStem
  ),
  (
  AWSCloudTrail
  | where UserAgent has_any (UserAgentString) or UserAgent matches regex UARegex
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventName
  ),
  (SigninLogs
  | where UserAgent has_any (UserAgentString) or UserAgent matches regex UARegex
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, Operation = OperationName, tostring(LocationDetails), tostring(DeviceDetail),    AppDisplayName, ClientAppUsed
  ),
  (AADNonInteractiveUserSignInLogs 
  | where UserAgent has_any (UserAgentString) or UserAgent matches regex UARegex
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, Operation = OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed
  ),
  (_Im_WebSession (httpuseragent_has_any=array_concat(UserAgentString,UARegexMinimalString))
  | where HttpUserAgent has_any (UserAgentString) or HttpUserAgent matches regex UARegex
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by HttpUserAgent, SourceIP = SrcIpAddr, DstIpAddr, Account = SrcUsername, Url, Type
  )
  )
entityMappings:
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: Url
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Account
version: 1.0.9
kind: Scheduled

Stages and Predicates

Parameters

let UserAgentString = dynamic (["${jndi:ldap:/", "${jndi:rmi:/", "${jndi:ldaps:/", "${jndi:dns:/", "${jndi:iiop:/","${jndi:","${jndi:nds:/","${jndi:corba/"]);
let UARegexMinimalString = dynamic(['{','%7b', '%7B']);

Let binding: UARegex

let UARegex = @'(\\$|%24)(\\{|%7B)([^jJ]*[jJ])([^nN]*[nN])([^dD]*[dD])([^iI]*[iI])(:|%3A|\\$|%24|}|%7D)';

union isfuzzy=true (7 sources)

Each leg below queries one source; the rule matches if any leg does. Sources: OfficeActivity, AzureDiagnostics, W3CIISLog, AWSCloudTrail, SigninLogs, AADNonInteractiveUserSignInLogs, _Im_WebSession

Leg 1: OfficeActivity

OfficeActivity
| where UserAgent has_any (UserAgentString) or UserAgent matches regex UARegex
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, Operation

Leg 2: AzureDiagnostics

AzureDiagnostics
| where Category in ("FrontdoorWebApplicationFirewallLog", "FrontdoorAccessLog", "ApplicationGatewayFirewallLog", "ApplicationGatewayAccessLog")
| where userAgent_s has_any (UserAgentString) or userAgent_s matches regex UARegex
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = userAgent_s, SourceIP = column_ifexists("clientIp_s",clientIP_s), Type, column_ifexists("originalHost_s",host_s), Url = requestUri_s, HttpStatus = column_ifexists("httpStatusDetails_s",httpStatus_d), column_ifexists("trackingReference_s",transactionId_g), ruleName_s, ResourceType, ResourceId

Leg 3: W3CIISLog

W3CIISLog
| where csUserAgent has_any (UserAgentString) or csUserAgent matches regex UARegex
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, Url = csUriStem

Leg 4: AWSCloudTrail

AWSCloudTrail
| where UserAgent has_any (UserAgentString) or UserAgent matches regex UARegex
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventName

Leg 5: SigninLogs

SigninLogs
| where UserAgent has_any (UserAgentString) or UserAgent matches regex UARegex
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, Operation = OperationName, tostring(LocationDetails), tostring(DeviceDetail),    AppDisplayName, ClientAppUsed

Leg 6: AADNonInteractiveUserSignInLogs

AADNonInteractiveUserSignInLogs 
| where UserAgent has_any (UserAgentString) or UserAgent matches regex UARegex
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, Operation = OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed

Leg 7: _Im_WebSession

_Im_WebSession (httpuseragent_has_any=array_concat(UserAgentString,UARegexMinimalString))
| where HttpUserAgent has_any (UserAgentString) or HttpUserAgent matches regex UARegex
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by HttpUserAgent, SourceIP = SrcIpAddr, DstIpAddr, Account = SrcUsername, Url, Type

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
  • ApplicationGatewayAccessLog transforms: cased
  • ApplicationGatewayFirewallLog transforms: cased
  • FrontdoorAccessLog transforms: cased
  • FrontdoorWebApplicationFirewallLog transforms: cased
HttpUserAgentmatch
  • ${jndi:
  • ${jndi:corba/
  • ${jndi:dns:/
  • ${jndi:iiop:/
  • ${jndi:ldap:/
  • ${jndi:ldaps:/
  • ${jndi:nds:/
  • ${jndi:rmi:/
HttpUserAgentregex_match
  • (\$|%24)(\{|%7B)([^jJ]*[jJ])([^nN]*[nN])([^dD]*[dD])([^iI]*[iI])(:|%3A|\$|%24|}|%7D)
UserAgentmatch
  • ${jndi:
  • ${jndi:corba/
  • ${jndi:dns:/
  • ${jndi:iiop:/
  • ${jndi:ldap:/
  • ${jndi:ldaps:/
  • ${jndi:nds:/
  • ${jndi:rmi:/
UserAgentregex_match
  • (\$|%24)(\{|%7B)([^jJ]*[jJ])([^nN]*[nN])([^dD]*[dD])([^iI]*[iI])(:|%3A|\$|%24|}|%7D)
csUserAgentmatch
  • ${jndi:
  • ${jndi:corba/
  • ${jndi:dns:/
  • ${jndi:iiop:/
  • ${jndi:ldap:/
  • ${jndi:ldaps:/
  • ${jndi:nds:/
  • ${jndi:rmi:/
csUserAgentregex_match
  • (\$|%24)(\{|%7B)([^jJ]*[jJ])([^nN]*[nN])([^dD]*[dD])([^iI]*[iI])(:|%3A|\$|%24|}|%7D)
userAgent_smatch
  • ${jndi:
  • ${jndi:corba/
  • ${jndi:dns:/
  • ${jndi:iiop:/
  • ${jndi:ldap:/
  • ${jndi:ldaps:/
  • ${jndi:nds:/
  • ${jndi:rmi:/
userAgent_sregex_match
  • (\$|%24)(\{|%7B)([^jJ]*[jJ])([^nN]*[nN])([^dD]*[dD])([^iI]*[iI])(:|%3A|\$|%24|}|%7D)

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
Accountsummarize
DstIpAddrsummarize
EndTimesummarize
HttpUserAgentsummarize
SourceIPsummarize
StartTimesummarize
Typesummarize
Urlsummarize