Detection rules › Kusto
Malformed user agent
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
| Tactic | Techniques |
|---|---|
| Initial Access | |
| Execution | |
| Command & Control |
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 =if
RecordType == "AzureActiveDirectoryStsLogon"UserAgent1else
UserAgent2Stage 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.
| Field | Kind | Values | Search |
|---|---|---|---|
OperationName | eq |
| field:"OperationName" kind:eq value:"ApplicationGatewayAccess" |
RecordType | in |
| field:"RecordType" kind:in |
ResourceType | eq |
| field:"ResourceType" kind:eq value:"APPLICATIONGATEWAYS" |
UserAgent | contains |
| field:"aws::userAgent" kind:contains value:"(Compatible;" |
UserAgent | is_not_null | field:"aws::userAgent" kind:is_not_null | |
UserAgent | ne |
| field:"aws::userAgent" kind:ne value:"-" |
UserAgent | regex_match |
| field:"aws::userAgent" kind:regex_match |
UserAgent | starts_with |
| field:"aws::userAgent" kind:starts_with |
UserAgent1 | is_not_null | field:"UserAgent1" kind:is_not_null | |
UserAgent2 | is_not_null | field:"UserAgent2" kind:is_not_null | |
csUserAgent | is_not_null | field:"csUserAgent" kind:is_not_null |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | summarize |
AppDisplayName | summarize |
ClientAppUsed | summarize |
EndTime | summarize |
OperationName | summarize |
SourceIP | summarize |
StartTime | summarize |
Type | summarize |
UserAgent | summarize |
AccountName | extend |
UPNSuffix | extend |