Repository navigation
[BUG] AWS CreateAccessKey: unquoted field names make the cross-user filter a no-op (fires on self-key creation) #4292
Description
Describe the bug
AWS CreateAccessKey (detections/cloud/aws_createaccesskey.yml, id 2a9b80d3-6340-4345-11ad-212bf3d0d111) is meant to surface a user creating an access key for a different user:
| eval match=if(match(userIdentity.userName,requestParameters.userName),1,0)
| search match=0
Inside eval, the dotted field names are not single-quoted, so SPL parses userIdentity.userName as the . concatenation of the (non-existent) fields userIdentity and userName. Both arguments to match() evaluate to null, match is always 0, and search match=0 keeps every event. As shipped, the filter does nothing: the rule fires on every successful non-console CreateAccessKey, including the routine case of a user rotating their own key.
Repro
Self-contained, with no data needed:
| makeresults count=2
| streamstats count as n
| eval _raw=if(n==1, "{\"userIdentity\":{\"userName\":\"alice\"},\"requestParameters\":{\"userName\":\"alice\"}}", "{\"userIdentity\":{\"userName\":\"alice\"},\"requestParameters\":{\"userName\":\"bob\"}}")
| spath
| eval case=if(n==1,"self: alice creates a key for alice","cross-user: alice creates a key for bob")
| eval match=if(match(userIdentity.userName,requestParameters.userName),1,0)
| eval match_quoted=if('userIdentity.userName'=='requestParameters.userName',1,0)
| table case userIdentity.userName requestParameters.userName match match_quoted
Output:
| case | userIdentity.userName | requestParameters.userName | match (shipped) | match_quoted |
|---|---|---|---|---|
| self: alice creates a key for alice | alice | alice | 0 | 1 |
| cross-user: alice creates a key for bob | alice | bob | 0 | 0 |
The self-key row should be excluded (match=1), but the shipped expression returns 0.
Expected behavior
Only cross-user key creation should produce a result. A minimal fix quotes the fields and compares them directly. match() also treats its second argument as a regex, so a username like svc. would match svcX:
| eval match=if('userIdentity.userName'=='requestParameters.userName',1,0)
| search match=0
One related case to consider: when requestParameters.userName is absent (the key is for the caller), the request is a self-key and should also be excluded, e.g. if(isnull('requestParameters.userName') OR 'userIdentity.userName'=='requestParameters.userName',1,0).
Additional context: the console exclusion no longer matches console calls
The base search excludes console activity with userAgent!=console.amazonaws.com. A real console-issued CreateAccessKey captured from an AWS account in 2026 records a browser user agent (Mozilla/5.0 ... Chrome/...) together with "sessionCredentialFromConsole": "true", not console.amazonaws.com. Keys created in the console today therefore aren't excluded. A scripted caller can also evade the rule by sending the literal console.amazonaws.com user agent.
sessionCredentialFromConsole is not a clean replacement key. It marks console-session credentials, not the client, and AWS's own log examples show CloudShell CLI calls (exec-env/CloudShell) carrying it, including IAM calls. Excluding on it would hide cross-user key creation scripted from CloudShell. Once the cross-user filter works, dropping the console exclusion altogether and suppressing known provisioning principals in aws_createaccesskey_filter may be the better trade-off.
App Version:
- ESCU: reproduced on 5.26.0; SPL unchanged on
develop(64acde7, rule version 11) - Splunk Enterprise 9.3.10, Splunk_TA_aws 7.11.0
Activity
nasbench commentedon Sep 29, 2026
Hey @vshcherbakov thanks for reporting this. Let me take a look and report back.