Detection rules › Kusto

Malformed user agent

Severity
medium
Time window
1d
Group by
Account, AppDisplayName, ClientAppUsed, EventName, EventSource, Operation, OperationName, RecordType, SourceIP, Type, UserAgent, csMethod, csUriStem, host_s, httpMethod_s, requestQuery_s, requestUri_s, sSiteName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Malware authors will sometimes hardcode user agent string values when writing the network communication component of their malware. Malformed user agents can be an indication of such malware.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

id: a357535e-f722-4afe-b375-cff362b2b376
name: Malformed user agent
description: |
  'Malware authors will sometimes hardcode user agent string values when writing the network communication component of their malware. Malformed user agents can be an indication of such malware.'
severity: Medium
requiredDataConnectors:
  - 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
  - CommandAndControl
  - Execution
relevantTechniques:
  - T1189
  - T1071
  - T1203
query: |
  (union isfuzzy=true
  (OfficeActivity | where UserAgent != ""),
  (OfficeActivity
  | where RecordType in ("AzureActiveDirectory", "AzureActiveDirectoryStsLogon")
  | extend OperationName = Operation
  | parse ExtendedProperties with * 'User-Agent\\":\\"' UserAgent2 '\\' *
  | parse ExtendedProperties with * 'UserAgent",      "Value": "' UserAgent1 '"' *
  | where isnotempty(UserAgent1) or isnotempty(UserAgent2)
  | extend UserAgent = iff( RecordType == 'AzureActiveDirectoryStsLogon', UserAgent1, UserAgent2)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, RecordType, Operation
  ),
  (AzureDiagnostics
  | where ResourceType =~ "APPLICATIONGATEWAYS"
  | where OperationName =~ "ApplicationGatewayAccess"
  | extend ClientIP = columnifexists("clientIP_s", "None"), UserAgent = columnifexists("userAgent_s", "None")
  | where UserAgent != '-'
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP,  requestUri_s, httpMethod_s, host_s, requestQuery_s, Type
  ),
  (
  W3CIISLog
  | where isnotempty(csUserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, csUriStem
  ),
  (
  AWSCloudTrail
  | where isnotempty(UserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventSource, EventName
  ),
  (SigninLogs
  | where isnotempty(UserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed
  ),
  (AADNonInteractiveUserSignInLogs
  | where isnotempty(UserAgent)
  | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed
  )
  )
  // Likely artefact of hardcoding
  | where UserAgent startswith "User" or UserAgent startswith '\"'
  // Incorrect casing
  or (UserAgent startswith "Mozilla" and not(UserAgent contains_cs "Mozilla"))
  // Incorrect casing
  or UserAgent contains_cs  "(Compatible;"
  // Missing MSIE version
  or UserAgent matches regex @"MSIE\s?;"
  // Incorrect spacing around MSIE version
  or UserAgent matches regex  @"MSIE(?:\d|.{1,5}?\d\s;)"
  | extend AccountName = split(Account, "@")[0], UPNSuffix = split(Account, "@")[1]
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: SourceIP
version: 1.0.6
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Stages and Predicates

Stage 1: union

union of 7 branches

Stage 2: source

OfficeActivity

Stage 3: where

| where UserAgent != ""

Stage 4: source

OfficeActivity

Stage 5: where

| where RecordType in ("AzureActiveDirectory", "AzureActiveDirectoryStsLogon")

Stage 6: extend

| extend OperationName = Operation

Stage 7: parse

| parse ExtendedProperties with * 'User-Agent\\":\\"' UserAgent2 '\\' *

Stage 8: parse

| parse ExtendedProperties with * 'UserAgent",      "Value": "' UserAgent1 '"' *

Stage 9: where

| where isnotempty(UserAgent1) or isnotempty(UserAgent2)

Stage 10: extend

| extend UserAgent = iff( RecordType == 'AzureActiveDirectoryStsLogon', UserAgent1, UserAgent2)
UserAgent =
ifRecordType == "AzureActiveDirectoryStsLogon"UserAgent1
elseUserAgent2

Stage 11: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP, Account = UserId, Type, RecordType, Operation

Stage 12: source

AzureDiagnostics

Stage 13: where

| where ResourceType =~ "APPLICATIONGATEWAYS"

Stage 14: where

| where OperationName =~ "ApplicationGatewayAccess"

Stage 15: extend

| extend ClientIP = columnifexists("clientIP_s", "None"), UserAgent = columnifexists("userAgent_s", "None")

Stage 16: where

| where UserAgent != '-'

Stage 17: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = ClientIP,  requestUri_s, httpMethod_s, host_s, requestQuery_s, Type

Stage 18: source

W3CIISLog

Stage 19: where

| where isnotempty(csUserAgent)

Stage 20: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent = csUserAgent, SourceIP = cIP, Account = csUserName, Type, sSiteName, csMethod, csUriStem

Stage 21: source

AWSCloudTrail

Stage 22: where

| where isnotempty(UserAgent)

Stage 23: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = SourceIpAddress, Account = UserIdentityUserName, Type, EventSource, EventName

Stage 24: source

SigninLogs

Stage 25: where

| where isnotempty(UserAgent)

Stage 26: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed

Stage 27: source

AADNonInteractiveUserSignInLogs

Stage 28: where

| where isnotempty(UserAgent)

Stage 29: summarize

| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by UserAgent, SourceIP = IPAddress, Account = UserPrincipalName, Type, OperationName, tostring(LocationDetails), tostring(DeviceDetail), AppDisplayName, ClientAppUsed

Stage 30: where

where ((not (UserAgent contains "Mozilla") and UserAgent startswith "Mozilla") or UserAgent contains "(Compatible;" or UserAgent matches regex @"MSIE(?:\d|.{1,5}?\d\s;)" or UserAgent matches regex @"MSIE\s?;" or UserAgent startswith "User" or UserAgent startswith "\"")

Stage 31: extend

extend AccountName, UPNSuffix

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
Accountsummarize
AppDisplayNamesummarize
ClientAppUsedsummarize
EndTimesummarize
OperationNamesummarize
SourceIPsummarize
StartTimesummarize
Typesummarize
UserAgentsummarize
AccountNameextend
UPNSuffixextend