From 50bd65f5736eb50e87dce39202ad2fcb4aac1ef2 Mon Sep 17 00:00:00 2001 From: merquiag Date: Tue, 22 Sep 2026 18:07:35 +0200 Subject: [PATCH 01/19] Add repeated CloudWatch Logs read operations detection --- ...peated_cloudwatch_logs_read_operations.yml | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml diff --git a/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml b/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml new file mode 100644 index 00000000000..9bc6ac89993 --- /dev/null +++ b/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml @@ -0,0 +1,117 @@ +name: AWS Repeated CloudWatch Logs Read Operations +id: 3f0ba9e1-c6cf-4317-a6bc-c4bcab2b5a06 +version: 1 +creation_date: '2026-09-21' +modification_date: '2026-09-21' +author: Maria Jose Erquiaga, Splunk +status: production +type: TTP +description: >- + The following analytic detects a high volume of Amazon CloudWatch Logs read + operations performed by the same AWS principal and account within a + five-minute period. It identifies more than 500 unique DescribeLogGroups, + DescribeLogStreams, DownloadLogEvents, FilterLogEvents, GetLogEvents, or + GetQueryResults API calls. This activity may indicate automated discovery or + collection of operational, application, or security telemetry from + CloudWatch Logs. The analytic counts unique CloudTrail event identifiers to + prevent duplicate delivery or ingestion from inflating the API-operation + count. +data_source: + - AWS CloudTrail +search: |- + `cloudtrail` eventSource="logs.amazonaws.com" + eventName IN ( + "DescribeLogGroups", + "DescribeLogStreams", + "DownloadLogEvents", + "FilterLogEvents", + "GetLogEvents", + "GetQueryResults" + ) + | eval user='userIdentity.principalId' + | eval vendor_account=coalesce( + vendor_account, + recipientAccountId, + 'userIdentity.accountId' + ) + | eval role_name='userIdentity.sessionContext.sessionIssuer.userName' + | eval signature=coalesce(signature, eventName) + | eval src=coalesce(src, sourceIPAddress) + | eval user_agent=coalesce(user_agent, userAgent) + | eval vendor_region=coalesce(vendor_region, awsRegion) + | where isnotnull(user) AND len(trim(user)) > 0 + | dedup eventID keepempty=true + | sort 0 _time + | streamstats time_window=5m count AS event_count + by user vendor_account + | where event_count > 500 + | stats + max(event_count) AS event_count + min(_time) AS firstTime + max(_time) AS lastTime + values(signature) AS api_operations + values(role_name) AS role_name + values(src) AS src + values(user_agent) AS user_agent + values(vendor_region) AS vendor_region + by user vendor_account + | `security_content_ctime(firstTime)` + | `security_content_ctime(lastTime)` + | `aws_repeated_cloudwatch_logs_read_operations_filter` +how_to_implement: >- + The Splunk Add-on for AWS is required to collect AWS CloudTrail events. + Configure CloudTrail to capture management Read events and ingest them with + the aws:cloudtrail sourcetype. The tested CloudWatch Logs operations are + recorded as management events, so a CloudWatch Logs data-event selector is + not required. The analytic uses a rolling five-minute window and counts + unique CloudTrail event identifiers to reduce duplicate-ingestion effects. +known_false_positives: >- + Automated log analytics, SIEM pipelines, incident-response tooling, backup + processes, and administrative troubleshooting may continuously read + CloudWatch Logs and generate a high volume of matching operations. The + prevalence of this activity has not yet been evaluated against broader + customer telemetry. Review the principal, account, source addresses, user + agents, Regions, and accessed CloudWatch Logs resources before escalating. +references: + - https://attack.mitre.org/techniques/T1530/ + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogGroups.html + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogStreams.html + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_FilterLogEvents.html + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetLogEvents.html + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetQueryResults.html +drilldown_searches: + - name: View CloudWatch Logs read operations for - "$user$" + search: '%original_detection_search% | search user="$user$" vendor_account="$vendor_account$"' + earliest_offset: $info_min_time$ + latest_offset: $info_max_time$ + - name: View risk events for the last 7 days for - "$user$" + search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' + earliest_offset: 7d + latest_offset: "0" +finding: + title: AWS principal $user$ performed $event_count$ CloudWatch Logs read operations + entity: + field: user + type: user + score: 50 +threat_objects: + - field: src + type: ip_address +analytic_story: + - Data Exfiltration +asset_type: AWS Account +mitre_attack_id: + - T1530 +product: + - Splunk Enterprise + - Splunk Enterprise Security + - Splunk Cloud +category: cloud +security_domain: threat +tests: + - name: True Positive Test + attack_data: + - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1530/aws_cloudwatch_logs_high_volume_retrieval/aws_cloudwatch_logs_high_volume_retrieval.json + sourcetype: aws:cloudtrail + source: aws_cloudtrail + test_type: unit From b96f672e57815a77e16cb518f39a52324694625b Mon Sep 17 00:00:00 2001 From: merquiag Date: Tue, 22 Sep 2026 18:08:10 +0200 Subject: [PATCH 02/19] Add repeated CloudWatch Logs read operations detection --- .../cloud/aws_repeated_cloudwatch_logs_read_operations.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml b/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml index 9bc6ac89993..375be9974c9 100644 --- a/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml +++ b/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml @@ -9,13 +9,11 @@ type: TTP description: >- The following analytic detects a high volume of Amazon CloudWatch Logs read operations performed by the same AWS principal and account within a - five-minute period. It identifies more than 500 unique DescribeLogGroups, + five-minute period. It identifies more than 500 DescribeLogGroups, DescribeLogStreams, DownloadLogEvents, FilterLogEvents, GetLogEvents, or GetQueryResults API calls. This activity may indicate automated discovery or collection of operational, application, or security telemetry from - CloudWatch Logs. The analytic counts unique CloudTrail event identifiers to - prevent duplicate delivery or ingestion from inflating the API-operation - count. + CloudWatch Logs. data_source: - AWS CloudTrail search: |- @@ -40,7 +38,6 @@ search: |- | eval user_agent=coalesce(user_agent, userAgent) | eval vendor_region=coalesce(vendor_region, awsRegion) | where isnotnull(user) AND len(trim(user)) > 0 - | dedup eventID keepempty=true | sort 0 _time | streamstats time_window=5m count AS event_count by user vendor_account From b2a63bd6b64ae3af71e451b0b0ac82a37647e77f Mon Sep 17 00:00:00 2001 From: merquiag Date: Tue, 29 Sep 2026 18:03:03 +0200 Subject: [PATCH 03/19] Add AWS CloudTrail behavioral detections --- ...malous_secrets_manager_batch_retrieval.yml | 87 +++++++++++++++++++ ...lic_keys_uploaded_by_a_single_identity.yml | 78 +++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml create mode 100644 detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml new file mode 100644 index 00000000000..95c3ce48a04 --- /dev/null +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -0,0 +1,87 @@ +name: AWS Anomalous Secrets Manager Batch Retrieval +id: 93646bda-291b-4a59-a926-19f08e26f7cd +version: 1 +creation_date: '2026-09-29' +modification_date: '2026-09-29' +author: Maria Jose Erquiaga, Splunk +status: production +type: TTP +description: The following analytic identifies a successful AWS Secrets Manager BatchGetSecretValue request made by a principal that has not successfully used this API during the preceding seven days. It evaluates the latest hour of activity against a seven-day baseline for each AWS principal. BatchGetSecretValue permits multiple secret values to be retrieved in one API request. An attacker with access to a compromised AWS identity may use this operation to collect application credentials, API tokens, database passwords, and other sensitive information while generating fewer CloudTrail events than repeated GetSecretValue calls. This activity is particularly significant when the principal does not normally perform batch secret retrieval. +data_source: + - AWS CloudTrail BatchGetSecretValue +search: |- + `cloudtrail` earliest=-169h latest=now eventName="BatchGetSecretValue" eventSource="secretsmanager.amazonaws.com" + | spath input=_raw path=userIdentity.userName output=iam_user + | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.userName output=role_name + | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.arn output=issuer_arn + | spath input=_raw path=userIdentity.arn output=identity_arn + | spath input=_raw path=userIdentity.principalId output=principal_id + | spath input=_raw path=recipientAccountId output=vendor_account + | spath input=_raw path=sourceIPAddress output=src + | spath input=_raw path=userAgent output=user_agent + | spath input=_raw path=awsRegion output=vendor_region + | spath input=_raw path=errorCode output=error_code + | eval user=coalesce(issuer_arn, identity_arn, principal_id, iam_user, role_name, "unknown") + | where (isnull(error_code) OR error_code="") AND user!="unknown" + | eval evaluation_start=relative_time(now(), "-1h") + | eval period=if(_time>=evaluation_start, "current", "baseline") + | stats count(eval(period="current")) as batch_request_count + count(eval(period="baseline")) as prior_batch_request_count + values(eval(if(period="current", src, null()))) as src + values(eval(if(period="current", user_agent, null()))) as user_agent + values(eval(if(period="current", vendor_account, null()))) as vendor_account + values(eval(if(period="current", vendor_region, null()))) as vendor_region + earliest(eval(if(period="current", _time, null()))) as firstTime + latest(eval(if(period="current", _time, null()))) as lastTime + by user + | where batch_request_count>0 AND prior_batch_request_count=0 + | `security_content_ctime(firstTime)` + | `security_content_ctime(lastTime)` + | `aws_anomalous_secrets_manager_batch_retrieval_filter` +how_to_implement: This detection requires the Splunk Add-on for Amazon Web Services and AWS CloudTrail events from AWS Secrets Manager. Schedule the search to run hourly. The search covers 169 hours, consisting of a one-hour evaluation window and the preceding 168-hour baseline. CloudTrail delivery latency must be considered when configuring the scheduled search. If CloudTrail events are regularly delayed, configure the schedule or evaluation window to allow sufficient ingestion time. The principal is derived from the session issuer ARN for assumed roles, the identity ARN for IAM users, and other identity attributes as fallbacks. The detection filter macro can be used to exclude authorized roles, applications, secret-rotation services, and deployment workflows. +known_false_positives: Legitimate administrators, deployment systems, credential-rotation services, incident-response tooling, or newly deployed applications may use BatchGetSecretValue for the first time. A principal may trigger the detection again when it has not used the API during the preceding seven days. Review the principal, source IP address, user agent, account, region, requested secret identifiers, and surrounding CloudTrail activity to determine whether the request was authorized. +references: + - https://attack.mitre.org/techniques/T1555/006/ + - https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_BatchGetSecretValue.html + - https://docs.aws.amazon.com/secretsmanager/latest/userguide/cloudtrail_log_entries.html + - https://www.elastic.co/guide/en/security/8.19/aws-secrets-manager-rapid-secrets-retrieval.html +drilldown_searches: + - name: View the detection results for - "$user$" + search: '%original_detection_search% | search user="$user$"' + earliest_offset: $info_min_time$ + latest_offset: $info_max_time$ + - name: View successful Secrets Manager activity for the last 7 days for - "$user$" + search: '`cloudtrail` eventSource="secretsmanager.amazonaws.com" | spath input=_raw path=userIdentity.userName output=iam_user | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.userName output=role_name | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.arn output=issuer_arn | spath input=_raw path=userIdentity.arn output=identity_arn | spath input=_raw path=userIdentity.principalId output=principal_id | spath input=_raw path=errorCode output=error_code | eval user=coalesce(issuer_arn, identity_arn, principal_id, iam_user, role_name, "unknown") | where (isnull(error_code) OR error_code="") AND user="$user$" | table _time user eventName sourceIPAddress userAgent awsRegion requestParameters' + earliest_offset: 7d + latest_offset: "0" + - name: View risk events for the last 7 days for - "$user$" + search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' + earliest_offset: 7d + latest_offset: "0" +intermediate_findings: + entities: + - field: user + type: user + score: 40 + message: User $user$ made $batch_request_count$ successful AWS Secrets Manager BatchGetSecretValue requests after no successful use of this API during the preceding seven days. +threat_objects: + - field: src + type: ip_address +analytic_story: + - AWS Identity and Access Management Account Takeover +asset_type: AWS Account +mitre_attack_id: + - T1555.006 +product: + - Splunk Cloud + - Splunk Enterprise + - Splunk Enterprise Security +category: cloud +security_domain: threat +tests: + - name: True Positive Test + attack_data: + - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1555.006/aws_anomalous_secrets_manager_batch_retrieval/aws_anomalous_secrets_manager_batch_retrieval.json + source: aws_cloudtrail + sourcetype: aws:cloudtrail + test_type: unit diff --git a/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml new file mode 100644 index 00000000000..854de5ed0da --- /dev/null +++ b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml @@ -0,0 +1,78 @@ +name: AWS EC2 Multiple SSH Public Keys Uploaded by a Single Identity +id: 62afb4a9-d9d0-464a-9326-aacf61c5a9f2 +version: 1 +date: '2026-09-29' +author: Maria Jose Erquiaga, Splunk +status: production +type: TTP +description: >- + Detects an AWS identity making 10 or more successful EC2 Instance Connect + SendSSHPublicKey API calls within the configured search interval. EC2 + Instance Connect temporarily makes an SSH public key available to an + operating-system user on an EC2 instance. A burst of these calls may + indicate automated attempts to establish access or move laterally using + compromised AWS credentials. The search counts distinct CloudTrail eventID + values to prevent duplicate ingestion from inflating the threshold. +data_source: +- AWS CloudTrail +search: >- + `cloudtrail` + eventSource="ec2-instance-connect.amazonaws.com" + eventName="SendSSHPublicKey" + responseElements.success=true + | eval actor=coalesce( + 'userIdentity.arn', + 'userIdentity.sessionContext.sessionIssuer.arn', + 'userIdentity.principalId' + ) + | stats + dc(eventID) as event_count + dc('requestParameters.instanceId') as unique_instances + values(awsRegion) as aws_regions + values('requestParameters.instanceId') as targeted_instances + values('requestParameters.instanceOSUser') as targeted_users + values(sourceIPAddress) as source_ip + values(userAgent) as user_agent + earliest(_time) as firstTime + latest(_time) as lastTime + by actor + | where event_count >= 10 + | eval duration_seconds=lastTime-firstTime + | `security_content_ctime(firstTime)` + | `security_content_ctime(lastTime)` + | `aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity_filter` +how_to_implement: >- + Ingest AWS CloudTrail management events into Splunk using the + aws:cloudtrail sourcetype. Ensure that successful EC2 Instance Connect + SendSSHPublicKey events are collected. Schedule the analytic with a short + lookback window appropriate for the environment, such as 10 minutes, and + run it every 5 minutes. The search uses distinct CloudTrail eventID values + so overlapping CloudTrail trails or duplicate collection paths do not + inflate the threshold. +known_false_positives: >- + Administrators and automated orchestration may legitimately use EC2 + Instance Connect repeatedly during fleet administration, troubleshooting, + or ephemeral-access workflows. Review the AWS identity, source addresses, + user agent, target instances, target operating-system users, and whether the + activity corresponds to an approved administrative process. +references: +- https://docs.aws.amazon.com/ec2-instance-connect/latest/APIReference/API_SendSSHPublicKey.html +- https://www.elastic.co/guide/en/security/8.19/prebuilt-rule-8-19-14-aws-ec2-instance-connect-ssh-public-key-uploaded.html +- https://stratus-red-team.cloud/attack-techniques/AWS/aws.lateral-movement.ec2-instance-connect/ +tags: + analytic_story: + - AWS Cloud Security + asset_type: AWS Account + mitre_attack_id: + - T1021.004 + - T1021.008 + product: + - Splunk Enterprise + - Splunk Enterprise Security + security_domain: threat +tests: +- name: True Positive Test + attack_data: + - data: https://raw.githubusercontent.com/splunk/security_content.git/attack_data/aws-ec2-multiple-ssh-public-keys-uploaded/datasets/attack_techniques/T1021.008/aws_ec2_multiple_ssh_public_keys_uploaded/aws_ec2_multiple_ssh_public_keys_uploaded.json + source: aws_cloudtrail + sourcetype: aws:cloudtrail From 5e95a8494e5ab8ef6b2a87ec72c7fb7797f30456 Mon Sep 17 00:00:00 2001 From: merquiag Date: Tue, 29 Sep 2026 18:59:14 +0200 Subject: [PATCH 04/19] Update AWS EC2 Instance Connect key upload detection --- ...lic_keys_uploaded_by_a_single_identity.yml | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml index 854de5ed0da..761444560f4 100644 --- a/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml +++ b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml @@ -1,7 +1,8 @@ name: AWS EC2 Multiple SSH Public Keys Uploaded by a Single Identity id: 62afb4a9-d9d0-464a-9326-aacf61c5a9f2 version: 1 -date: '2026-09-29' +creation_date: '2026-09-29' +modification_date: '2026-09-29' author: Maria Jose Erquiaga, Splunk status: production type: TTP @@ -14,19 +15,11 @@ description: >- compromised AWS credentials. The search counts distinct CloudTrail eventID values to prevent duplicate ingestion from inflating the threshold. data_source: -- AWS CloudTrail -search: >- - `cloudtrail` - eventSource="ec2-instance-connect.amazonaws.com" - eventName="SendSSHPublicKey" - responseElements.success=true - | eval actor=coalesce( - 'userIdentity.arn', - 'userIdentity.sessionContext.sessionIssuer.arn', - 'userIdentity.principalId' - ) - | stats - dc(eventID) as event_count + - AWS CloudTrail +search: |- + `cloudtrail` eventSource="ec2-instance-connect.amazonaws.com" eventName="SendSSHPublicKey" responseElements.success=true + | eval actor=coalesce('userIdentity.arn', 'userIdentity.sessionContext.sessionIssuer.arn', 'userIdentity.principalId') + | stats dc(eventID) as event_count dc('requestParameters.instanceId') as unique_instances values(awsRegion) as aws_regions values('requestParameters.instanceId') as targeted_instances @@ -46,33 +39,44 @@ how_to_implement: >- aws:cloudtrail sourcetype. Ensure that successful EC2 Instance Connect SendSSHPublicKey events are collected. Schedule the analytic with a short lookback window appropriate for the environment, such as 10 minutes, and - run it every 5 minutes. The search uses distinct CloudTrail eventID values + run it every 5 minutes. The search counts distinct CloudTrail eventID values so overlapping CloudTrail trails or duplicate collection paths do not inflate the threshold. known_false_positives: >- Administrators and automated orchestration may legitimately use EC2 Instance Connect repeatedly during fleet administration, troubleshooting, or ephemeral-access workflows. Review the AWS identity, source addresses, - user agent, target instances, target operating-system users, and whether the - activity corresponds to an approved administrative process. + user agent, target instances, target operating-system users, and whether + the activity corresponds to an approved administrative process. references: -- https://docs.aws.amazon.com/ec2-instance-connect/latest/APIReference/API_SendSSHPublicKey.html -- https://www.elastic.co/guide/en/security/8.19/prebuilt-rule-8-19-14-aws-ec2-instance-connect-ssh-public-key-uploaded.html -- https://stratus-red-team.cloud/attack-techniques/AWS/aws.lateral-movement.ec2-instance-connect/ -tags: - analytic_story: - - AWS Cloud Security - asset_type: AWS Account - mitre_attack_id: + - https://docs.aws.amazon.com/ec2-instance-connect/latest/APIReference/API_SendSSHPublicKey.html + - https://stratus-red-team.cloud/attack-techniques/AWS/aws.lateral-movement.ec2-instance-connect/ + - https://www.elastic.co/guide/en/security/8.19/prebuilt-rule-8-19-14-aws-ec2-instance-connect-ssh-public-key-uploaded.html +finding: + title: AWS identity $actor$ uploaded $event_count$ SSH public keys using EC2 Instance Connect + entity: + field: actor + type: user + score: 50 +threat_objects: + - field: source_ip + type: ip_address +analytic_story: + - Suspicious Cloud Instance Activities +asset_type: AWS Account +mitre_attack_id: - T1021.004 - T1021.008 - product: +product: + - Splunk Cloud - Splunk Enterprise - Splunk Enterprise Security - security_domain: threat +category: cloud +security_domain: threat tests: -- name: True Positive Test - attack_data: - - data: https://raw.githubusercontent.com/splunk/security_content.git/attack_data/aws-ec2-multiple-ssh-public-keys-uploaded/datasets/attack_techniques/T1021.008/aws_ec2_multiple_ssh_public_keys_uploaded/aws_ec2_multiple_ssh_public_keys_uploaded.json - source: aws_cloudtrail - sourcetype: aws:cloudtrail + - name: True Positive Test + attack_data: + - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1021.008/aws_ec2_multiple_ssh_public_keys_uploaded/aws_ec2_multiple_ssh_public_keys_uploaded.json + sourcetype: aws:cloudtrail + source: aws_cloudtrail + test_type: unit From b018e2cd4add69cad4378f36c2339141fc8b79f4 Mon Sep 17 00:00:00 2001 From: merquiag Date: Wed, 30 Sep 2026 13:32:39 +0200 Subject: [PATCH 05/19] Update AWS cloud detections and validation references --- ...malous_secrets_manager_batch_retrieval.yml | 2 +- ...lic_keys_uploaded_by_a_single_identity.yml | 117 ++++++------ ...peated_cloudwatch_logs_read_operations.yml | 180 +++++++++--------- 3 files changed, 149 insertions(+), 150 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index 95c3ce48a04..d25e4a6594c 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -8,7 +8,7 @@ status: production type: TTP description: The following analytic identifies a successful AWS Secrets Manager BatchGetSecretValue request made by a principal that has not successfully used this API during the preceding seven days. It evaluates the latest hour of activity against a seven-day baseline for each AWS principal. BatchGetSecretValue permits multiple secret values to be retrieved in one API request. An attacker with access to a compromised AWS identity may use this operation to collect application credentials, API tokens, database passwords, and other sensitive information while generating fewer CloudTrail events than repeated GetSecretValue calls. This activity is particularly significant when the principal does not normally perform batch secret retrieval. data_source: - - AWS CloudTrail BatchGetSecretValue + - AWS CloudTrail search: |- `cloudtrail` earliest=-169h latest=now eventName="BatchGetSecretValue" eventSource="secretsmanager.amazonaws.com" | spath input=_raw path=userIdentity.userName output=iam_user diff --git a/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml index 761444560f4..dec341b84b9 100644 --- a/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml +++ b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml @@ -7,76 +7,75 @@ author: Maria Jose Erquiaga, Splunk status: production type: TTP description: >- - Detects an AWS identity making 10 or more successful EC2 Instance Connect - SendSSHPublicKey API calls within the configured search interval. EC2 - Instance Connect temporarily makes an SSH public key available to an - operating-system user on an EC2 instance. A burst of these calls may - indicate automated attempts to establish access or move laterally using - compromised AWS credentials. The search counts distinct CloudTrail eventID - values to prevent duplicate ingestion from inflating the threshold. + Detects an AWS identity making 10 or more successful EC2 Instance Connect + SendSSHPublicKey API calls within the configured search interval. EC2 + Instance Connect temporarily makes an SSH public key available to an + operating-system user on an EC2 instance. A burst of these calls may + indicate automated attempts to establish access or move laterally using + compromised AWS credentials. data_source: - - AWS CloudTrail + - AWS CloudTrail search: |- - `cloudtrail` eventSource="ec2-instance-connect.amazonaws.com" eventName="SendSSHPublicKey" responseElements.success=true - | eval actor=coalesce('userIdentity.arn', 'userIdentity.sessionContext.sessionIssuer.arn', 'userIdentity.principalId') - | stats dc(eventID) as event_count - dc('requestParameters.instanceId') as unique_instances - values(awsRegion) as aws_regions - values('requestParameters.instanceId') as targeted_instances - values('requestParameters.instanceOSUser') as targeted_users - values(sourceIPAddress) as source_ip - values(userAgent) as user_agent - earliest(_time) as firstTime - latest(_time) as lastTime - by actor - | where event_count >= 10 - | eval duration_seconds=lastTime-firstTime - | `security_content_ctime(firstTime)` - | `security_content_ctime(lastTime)` - | `aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity_filter` + `cloudtrail` eventSource="ec2-instance-connect.amazonaws.com" eventName="SendSSHPublicKey" responseElements.success=true + | eval actor=coalesce('userIdentity.arn', 'userIdentity.sessionContext.sessionIssuer.arn', 'userIdentity.principalId') + | stats count as event_count + dc('requestParameters.instanceId') as unique_instances + values(awsRegion) as aws_regions + values('requestParameters.instanceId') as targeted_instances + values('requestParameters.instanceOSUser') as targeted_users + values(sourceIPAddress) as source_ip + values(userAgent) as user_agent + earliest(_time) as firstTime + latest(_time) as lastTime + by actor + | where event_count >= 10 + | eval duration_seconds=lastTime-firstTime + | `security_content_ctime(firstTime)` + | `security_content_ctime(lastTime)` + | `aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity_filter` how_to_implement: >- - Ingest AWS CloudTrail management events into Splunk using the - aws:cloudtrail sourcetype. Ensure that successful EC2 Instance Connect - SendSSHPublicKey events are collected. Schedule the analytic with a short - lookback window appropriate for the environment, such as 10 minutes, and - run it every 5 minutes. The search counts distinct CloudTrail eventID values - so overlapping CloudTrail trails or duplicate collection paths do not - inflate the threshold. + Ingest AWS CloudTrail management events into Splunk using the + aws:cloudtrail sourcetype. Ensure that successful EC2 Instance Connect + SendSSHPublicKey events are collected. Schedule the analytic with a short + lookback window appropriate for the environment, such as 10 minutes, and + run it every 5 minutes. The search counts distinct CloudTrail eventID values + so overlapping CloudTrail trails or duplicate collection paths do not + inflate the threshold. known_false_positives: >- - Administrators and automated orchestration may legitimately use EC2 - Instance Connect repeatedly during fleet administration, troubleshooting, - or ephemeral-access workflows. Review the AWS identity, source addresses, - user agent, target instances, target operating-system users, and whether - the activity corresponds to an approved administrative process. + Administrators and automated orchestration may legitimately use EC2 + Instance Connect repeatedly during fleet administration, troubleshooting, + or ephemeral-access workflows. Review the AWS identity, source addresses, + user agent, target instances, target operating-system users, and whether + the activity corresponds to an approved administrative process. references: - - https://docs.aws.amazon.com/ec2-instance-connect/latest/APIReference/API_SendSSHPublicKey.html - - https://stratus-red-team.cloud/attack-techniques/AWS/aws.lateral-movement.ec2-instance-connect/ - - https://www.elastic.co/guide/en/security/8.19/prebuilt-rule-8-19-14-aws-ec2-instance-connect-ssh-public-key-uploaded.html + - https://docs.aws.amazon.com/ec2-instance-connect/latest/APIReference/API_SendSSHPublicKey.html + - https://stratus-red-team.cloud/attack-techniques/AWS/aws.lateral-movement.ec2-instance-connect/ + - https://www.elastic.co/guide/en/security/8.19/prebuilt-rule-8-19-14-aws-ec2-instance-connect-ssh-public-key-uploaded.html finding: - title: AWS identity $actor$ uploaded $event_count$ SSH public keys using EC2 Instance Connect - entity: - field: actor - type: user - score: 50 + title: AWS identity $actor$ uploaded $event_count$ SSH public keys using EC2 Instance Connect + entity: + field: actor + type: user + score: 50 threat_objects: - - field: source_ip - type: ip_address + - field: source_ip + type: ip_address analytic_story: - - Suspicious Cloud Instance Activities + - Suspicious Cloud Instance Activities asset_type: AWS Account mitre_attack_id: - - T1021.004 - - T1021.008 + - T1021.004 + - T1021.008 product: - - Splunk Cloud - - Splunk Enterprise - - Splunk Enterprise Security + - Splunk Cloud + - Splunk Enterprise + - Splunk Enterprise Security category: cloud security_domain: threat tests: - - name: True Positive Test - attack_data: - - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1021.008/aws_ec2_multiple_ssh_public_keys_uploaded/aws_ec2_multiple_ssh_public_keys_uploaded.json - sourcetype: aws:cloudtrail - source: aws_cloudtrail - test_type: unit + - name: True Positive Test + attack_data: + - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1021.008/aws_ec2_multiple_ssh_public_keys_uploaded/aws_ec2_multiple_ssh_public_keys_uploaded.json + sourcetype: aws:cloudtrail + source: aws_cloudtrail + test_type: unit diff --git a/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml b/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml index 375be9974c9..0dd66381bc2 100644 --- a/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml +++ b/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml @@ -7,108 +7,108 @@ author: Maria Jose Erquiaga, Splunk status: production type: TTP description: >- - The following analytic detects a high volume of Amazon CloudWatch Logs read - operations performed by the same AWS principal and account within a - five-minute period. It identifies more than 500 DescribeLogGroups, - DescribeLogStreams, DownloadLogEvents, FilterLogEvents, GetLogEvents, or - GetQueryResults API calls. This activity may indicate automated discovery or - collection of operational, application, or security telemetry from - CloudWatch Logs. + The following analytic detects a high volume of Amazon CloudWatch Logs read + operations performed by the same AWS principal and account within a + five-minute period. It identifies more than 500 DescribeLogGroups, + DescribeLogStreams, DownloadLogEvents, FilterLogEvents, GetLogEvents, or + GetQueryResults API calls. This activity may indicate automated discovery or + collection of operational, application, or security telemetry from + CloudWatch Logs. data_source: - - AWS CloudTrail + - AWS CloudTrail search: |- - `cloudtrail` eventSource="logs.amazonaws.com" - eventName IN ( - "DescribeLogGroups", - "DescribeLogStreams", - "DownloadLogEvents", - "FilterLogEvents", - "GetLogEvents", - "GetQueryResults" - ) - | eval user='userIdentity.principalId' - | eval vendor_account=coalesce( - vendor_account, - recipientAccountId, - 'userIdentity.accountId' - ) - | eval role_name='userIdentity.sessionContext.sessionIssuer.userName' - | eval signature=coalesce(signature, eventName) - | eval src=coalesce(src, sourceIPAddress) - | eval user_agent=coalesce(user_agent, userAgent) - | eval vendor_region=coalesce(vendor_region, awsRegion) - | where isnotnull(user) AND len(trim(user)) > 0 - | sort 0 _time - | streamstats time_window=5m count AS event_count - by user vendor_account - | where event_count > 500 - | stats - max(event_count) AS event_count - min(_time) AS firstTime - max(_time) AS lastTime - values(signature) AS api_operations - values(role_name) AS role_name - values(src) AS src - values(user_agent) AS user_agent - values(vendor_region) AS vendor_region - by user vendor_account - | `security_content_ctime(firstTime)` - | `security_content_ctime(lastTime)` - | `aws_repeated_cloudwatch_logs_read_operations_filter` + `cloudtrail` eventSource="logs.amazonaws.com" + eventName IN ( + "DescribeLogGroups", + "DescribeLogStreams", + "DownloadLogEvents", + "FilterLogEvents", + "GetLogEvents", + "GetQueryResults" + ) + | eval user='userIdentity.principalId' + | eval vendor_account=coalesce( + vendor_account, + recipientAccountId, + 'userIdentity.accountId' + ) + | eval role_name='userIdentity.sessionContext.sessionIssuer.userName' + | eval signature=coalesce(signature, eventName) + | eval src=coalesce(src, sourceIPAddress) + | eval user_agent=coalesce(user_agent, userAgent) + | eval vendor_region=coalesce(vendor_region, awsRegion) + | where isnotnull(user) AND len(trim(user)) > 0 + | sort 0 _time + | streamstats time_window=5m count AS event_count + by user vendor_account + | where event_count > 500 + | stats + max(event_count) AS event_count + min(_time) AS firstTime + max(_time) AS lastTime + values(signature) AS api_operations + values(role_name) AS role_name + values(src) AS src + values(user_agent) AS user_agent + values(vendor_region) AS vendor_region + by user vendor_account + | `security_content_ctime(firstTime)` + | `security_content_ctime(lastTime)` + | `aws_repeated_cloudwatch_logs_read_operations_filter` how_to_implement: >- - The Splunk Add-on for AWS is required to collect AWS CloudTrail events. - Configure CloudTrail to capture management Read events and ingest them with - the aws:cloudtrail sourcetype. The tested CloudWatch Logs operations are - recorded as management events, so a CloudWatch Logs data-event selector is - not required. The analytic uses a rolling five-minute window and counts - unique CloudTrail event identifiers to reduce duplicate-ingestion effects. + The Splunk Add-on for AWS is required to collect AWS CloudTrail events. + Configure CloudTrail to capture management Read events and ingest them with + the aws:cloudtrail sourcetype. The tested CloudWatch Logs operations are + recorded as management events, so a CloudWatch Logs data-event selector is + not required. The analytic uses a rolling five-minute window and counts + unique CloudTrail event identifiers to reduce duplicate-ingestion effects. known_false_positives: >- - Automated log analytics, SIEM pipelines, incident-response tooling, backup - processes, and administrative troubleshooting may continuously read - CloudWatch Logs and generate a high volume of matching operations. The - prevalence of this activity has not yet been evaluated against broader - customer telemetry. Review the principal, account, source addresses, user - agents, Regions, and accessed CloudWatch Logs resources before escalating. + Automated log analytics, SIEM pipelines, incident-response tooling, backup + processes, and administrative troubleshooting may continuously read + CloudWatch Logs and generate a high volume of matching operations. The + prevalence of this activity has not yet been evaluated against broader + customer telemetry. Review the principal, account, source addresses, user + agents, Regions, and accessed CloudWatch Logs resources before escalating. references: - - https://attack.mitre.org/techniques/T1530/ - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogGroups.html - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogStreams.html - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_FilterLogEvents.html - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetLogEvents.html - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetQueryResults.html + - https://attack.mitre.org/techniques/T1530/ + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogGroups.html + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogStreams.html + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_FilterLogEvents.html + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetLogEvents.html + - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetQueryResults.html drilldown_searches: - - name: View CloudWatch Logs read operations for - "$user$" - search: '%original_detection_search% | search user="$user$" vendor_account="$vendor_account$"' - earliest_offset: $info_min_time$ - latest_offset: $info_max_time$ - - name: View risk events for the last 7 days for - "$user$" - search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' - earliest_offset: 7d - latest_offset: "0" + - name: View CloudWatch Logs read operations for - "$user$" + search: '%original_detection_search% | search user="$user$" vendor_account="$vendor_account$"' + earliest_offset: $info_min_time$ + latest_offset: $info_max_time$ + - name: View risk events for the last 7 days for - "$user$" + search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' + earliest_offset: 7d + latest_offset: "0" finding: - title: AWS principal $user$ performed $event_count$ CloudWatch Logs read operations - entity: - field: user - type: user - score: 50 + title: AWS principal $user$ performed $event_count$ CloudWatch Logs read operations + entity: + field: user + type: user + score: 50 threat_objects: - - field: src - type: ip_address + - field: src + type: ip_address analytic_story: - - Data Exfiltration + - Data Exfiltration asset_type: AWS Account mitre_attack_id: - - T1530 + - T1530 product: - - Splunk Enterprise - - Splunk Enterprise Security - - Splunk Cloud + - Splunk Enterprise + - Splunk Enterprise Security + - Splunk Cloud category: cloud security_domain: threat tests: - - name: True Positive Test - attack_data: - - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1530/aws_cloudwatch_logs_high_volume_retrieval/aws_cloudwatch_logs_high_volume_retrieval.json - sourcetype: aws:cloudtrail - source: aws_cloudtrail - test_type: unit + - name: True Positive Test + attack_data: + - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1530/aws_cloudwatch_logs_high_volume_retrieval/aws_cloudwatch_logs_high_volume_retrieval.json + sourcetype: aws:cloudtrail + source: aws_cloudtrail + test_type: unit From 64af8583b8ef460642922848176a87e0f3f01620 Mon Sep 17 00:00:00 2001 From: merquiag Date: Wed, 30 Sep 2026 14:15:07 +0200 Subject: [PATCH 06/19] Fix finding for AWS Secrets Manager detection --- ...aws_anomalous_secrets_manager_batch_retrieval.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index d25e4a6594c..5d6f9123210 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -58,12 +58,12 @@ drilldown_searches: search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' earliest_offset: 7d latest_offset: "0" -intermediate_findings: - entities: - - field: user - type: user - score: 40 - message: User $user$ made $batch_request_count$ successful AWS Secrets Manager BatchGetSecretValue requests after no successful use of this API during the preceding seven days. +finding: + title: User $user$ made $batch_request_count$ successful AWS Secrets Manager BatchGetSecretValue requests after no successful use of this API during the preceding seven days + entity: + field: user + type: user + score: 40 threat_objects: - field: src type: ip_address From a87f707e067d1c56f9e46bbf3517be390ae6c6eb Mon Sep 17 00:00:00 2001 From: merquiag Date: Wed, 30 Sep 2026 18:50:43 +0200 Subject: [PATCH 07/19] Fix time window for Secrets Manager batch retrieval --- ...malous_secrets_manager_batch_retrieval.yml | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index 5d6f9123210..2e8d90bdace 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -6,11 +6,19 @@ modification_date: '2026-09-29' author: Maria Jose Erquiaga, Splunk status: production type: TTP -description: The following analytic identifies a successful AWS Secrets Manager BatchGetSecretValue request made by a principal that has not successfully used this API during the preceding seven days. It evaluates the latest hour of activity against a seven-day baseline for each AWS principal. BatchGetSecretValue permits multiple secret values to be retrieved in one API request. An attacker with access to a compromised AWS identity may use this operation to collect application credentials, API tokens, database passwords, and other sensitive information while generating fewer CloudTrail events than repeated GetSecretValue calls. This activity is particularly significant when the principal does not normally perform batch secret retrieval. +description: >- + The following analytic detects successful AWS Secrets Manager + BatchGetSecretValue requests from a principal that did not successfully use + this API during the previous seven days. It compares activity from the + latest hour with the preceding seven-day baseline. BatchGetSecretValue can + retrieve multiple secrets in one request, allowing an attacker using a + compromised AWS identity to collect credentials, tokens, passwords, and + other sensitive information while generating fewer CloudTrail events than + repeated GetSecretValue calls. data_source: - AWS CloudTrail search: |- - `cloudtrail` earliest=-169h latest=now eventName="BatchGetSecretValue" eventSource="secretsmanager.amazonaws.com" + `cloudtrail` eventName="BatchGetSecretValue" eventSource="secretsmanager.amazonaws.com" | spath input=_raw path=userIdentity.userName output=iam_user | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.userName output=role_name | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.arn output=issuer_arn @@ -23,7 +31,9 @@ search: |- | spath input=_raw path=errorCode output=error_code | eval user=coalesce(issuer_arn, identity_arn, principal_id, iam_user, role_name, "unknown") | where (isnull(error_code) OR error_code="") AND user!="unknown" - | eval evaluation_start=relative_time(now(), "-1h") + | addinfo + | eval evaluation_start=relative_time(info_max_time, "-1h"), baseline_start=relative_time(info_max_time, "-169h") + | where _time>=baseline_start AND _time<=info_max_time | eval period=if(_time>=evaluation_start, "current", "baseline") | stats count(eval(period="current")) as batch_request_count count(eval(period="baseline")) as prior_batch_request_count @@ -38,7 +48,14 @@ search: |- | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `aws_anomalous_secrets_manager_batch_retrieval_filter` -how_to_implement: This detection requires the Splunk Add-on for Amazon Web Services and AWS CloudTrail events from AWS Secrets Manager. Schedule the search to run hourly. The search covers 169 hours, consisting of a one-hour evaluation window and the preceding 168-hour baseline. CloudTrail delivery latency must be considered when configuring the scheduled search. If CloudTrail events are regularly delayed, configure the schedule or evaluation window to allow sufficient ingestion time. The principal is derived from the session issuer ARN for assumed roles, the identity ARN for IAM users, and other identity attributes as fallbacks. The detection filter macro can be used to exclude authorized roles, applications, secret-rotation services, and deployment workflows. +how_to_implement: >- + Ingest AWS CloudTrail management events into Splunk using the + aws:cloudtrail sourcetype. Configure the scheduled search to run hourly + with a 169-hour lookback. The analytic treats the latest hour as the + current evaluation period and the preceding 168 hours as the baseline. + Ensure the search has a finite latest-time boundary because the current + and baseline periods are derived from info_max_time. Adjust the scheduling + delay as necessary to account for CloudTrail ingestion latency. known_false_positives: Legitimate administrators, deployment systems, credential-rotation services, incident-response tooling, or newly deployed applications may use BatchGetSecretValue for the first time. A principal may trigger the detection again when it has not used the API during the preceding seven days. Review the principal, source IP address, user agent, account, region, requested secret identifiers, and surrounding CloudTrail activity to determine whether the request was authorized. references: - https://attack.mitre.org/techniques/T1555/006/ From 4f0e029ff8085757e8b9e0264dec89c217b41b01 Mon Sep 17 00:00:00 2001 From: merquiag Date: Wed, 30 Sep 2026 21:21:00 +0200 Subject: [PATCH 08/19] Update Secrets Manager baseline test configuration --- .../aws_anomalous_secrets_manager_batch_retrieval.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index 2e8d90bdace..118d2a2fd21 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -101,4 +101,10 @@ tests: - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1555.006/aws_anomalous_secrets_manager_batch_retrieval/aws_anomalous_secrets_manager_batch_retrieval.json source: aws_cloudtrail sourcetype: aws:cloudtrail - test_type: unit + description: >- + This analytic requires a 169-hour search window so it can compare + activity in the latest hour with the preceding seven-day baseline. + The test data contains the current-period event but does not reproduce + the complete scheduled-search time context required for an automated + unit test. + test_type: experimental From 24f86e263e91aeb1b5f00e22b749cc28fde9cb75 Mon Sep 17 00:00:00 2001 From: merquiag Date: Fri, 2 Oct 2026 12:49:30 +0200 Subject: [PATCH 09/19] Use CIM fields in AWS EC2 key upload detection --- ...public_keys_uploaded_by_a_single_identity.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml index dec341b84b9..6823486b32c 100644 --- a/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml +++ b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml @@ -17,17 +17,17 @@ data_source: - AWS CloudTrail search: |- `cloudtrail` eventSource="ec2-instance-connect.amazonaws.com" eventName="SendSSHPublicKey" responseElements.success=true - | eval actor=coalesce('userIdentity.arn', 'userIdentity.sessionContext.sessionIssuer.arn', 'userIdentity.principalId') + | eval user=coalesce(user_name, 'userIdentity.arn', 'userIdentity.principalId') | stats count as event_count dc('requestParameters.instanceId') as unique_instances - values(awsRegion) as aws_regions values('requestParameters.instanceId') as targeted_instances values('requestParameters.instanceOSUser') as targeted_users - values(sourceIPAddress) as source_ip - values(userAgent) as user_agent + values(src) as src + values(user_agent) as user_agent + values(vendor_region) as vendor_region earliest(_time) as firstTime latest(_time) as lastTime - by actor + by user | where event_count >= 10 | eval duration_seconds=lastTime-firstTime | `security_content_ctime(firstTime)` @@ -52,13 +52,13 @@ references: - https://stratus-red-team.cloud/attack-techniques/AWS/aws.lateral-movement.ec2-instance-connect/ - https://www.elastic.co/guide/en/security/8.19/prebuilt-rule-8-19-14-aws-ec2-instance-connect-ssh-public-key-uploaded.html finding: - title: AWS identity $actor$ uploaded $event_count$ SSH public keys using EC2 Instance Connect + title: AWS identity $user$ uploaded $event_count$ SSH public keys using EC2 Instance Connect entity: - field: actor + field: user type: user score: 50 threat_objects: - - field: source_ip + - field: src type: ip_address analytic_story: - Suspicious Cloud Instance Activities From f98ef83c0f1c061104a8d3183311832b82bfae0d Mon Sep 17 00:00:00 2001 From: themaryjo Date: Thu, 8 Oct 2026 18:01:01 +0200 Subject: [PATCH 10/19] Delete detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml Was pushed in another PR: https://github.com/splunk/security_content/pull/4312/changes --- ...lic_keys_uploaded_by_a_single_identity.yml | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml diff --git a/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml deleted file mode 100644 index 6823486b32c..00000000000 --- a/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: AWS EC2 Multiple SSH Public Keys Uploaded by a Single Identity -id: 62afb4a9-d9d0-464a-9326-aacf61c5a9f2 -version: 1 -creation_date: '2026-09-29' -modification_date: '2026-09-29' -author: Maria Jose Erquiaga, Splunk -status: production -type: TTP -description: >- - Detects an AWS identity making 10 or more successful EC2 Instance Connect - SendSSHPublicKey API calls within the configured search interval. EC2 - Instance Connect temporarily makes an SSH public key available to an - operating-system user on an EC2 instance. A burst of these calls may - indicate automated attempts to establish access or move laterally using - compromised AWS credentials. -data_source: - - AWS CloudTrail -search: |- - `cloudtrail` eventSource="ec2-instance-connect.amazonaws.com" eventName="SendSSHPublicKey" responseElements.success=true - | eval user=coalesce(user_name, 'userIdentity.arn', 'userIdentity.principalId') - | stats count as event_count - dc('requestParameters.instanceId') as unique_instances - values('requestParameters.instanceId') as targeted_instances - values('requestParameters.instanceOSUser') as targeted_users - values(src) as src - values(user_agent) as user_agent - values(vendor_region) as vendor_region - earliest(_time) as firstTime - latest(_time) as lastTime - by user - | where event_count >= 10 - | eval duration_seconds=lastTime-firstTime - | `security_content_ctime(firstTime)` - | `security_content_ctime(lastTime)` - | `aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity_filter` -how_to_implement: >- - Ingest AWS CloudTrail management events into Splunk using the - aws:cloudtrail sourcetype. Ensure that successful EC2 Instance Connect - SendSSHPublicKey events are collected. Schedule the analytic with a short - lookback window appropriate for the environment, such as 10 minutes, and - run it every 5 minutes. The search counts distinct CloudTrail eventID values - so overlapping CloudTrail trails or duplicate collection paths do not - inflate the threshold. -known_false_positives: >- - Administrators and automated orchestration may legitimately use EC2 - Instance Connect repeatedly during fleet administration, troubleshooting, - or ephemeral-access workflows. Review the AWS identity, source addresses, - user agent, target instances, target operating-system users, and whether - the activity corresponds to an approved administrative process. -references: - - https://docs.aws.amazon.com/ec2-instance-connect/latest/APIReference/API_SendSSHPublicKey.html - - https://stratus-red-team.cloud/attack-techniques/AWS/aws.lateral-movement.ec2-instance-connect/ - - https://www.elastic.co/guide/en/security/8.19/prebuilt-rule-8-19-14-aws-ec2-instance-connect-ssh-public-key-uploaded.html -finding: - title: AWS identity $user$ uploaded $event_count$ SSH public keys using EC2 Instance Connect - entity: - field: user - type: user - score: 50 -threat_objects: - - field: src - type: ip_address -analytic_story: - - Suspicious Cloud Instance Activities -asset_type: AWS Account -mitre_attack_id: - - T1021.004 - - T1021.008 -product: - - Splunk Cloud - - Splunk Enterprise - - Splunk Enterprise Security -category: cloud -security_domain: threat -tests: - - name: True Positive Test - attack_data: - - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1021.008/aws_ec2_multiple_ssh_public_keys_uploaded/aws_ec2_multiple_ssh_public_keys_uploaded.json - sourcetype: aws:cloudtrail - source: aws_cloudtrail - test_type: unit From da1dd0f9303ef61b78f280f8fe27eefe78b52123 Mon Sep 17 00:00:00 2001 From: themaryjo Date: Fri, 9 Oct 2026 17:33:40 +0200 Subject: [PATCH 11/19] modification of secrets manager detection We changed the logic and I applied changes in the use of fields after review --- ...malous_secrets_manager_batch_retrieval.yml | 78 +++++++------------ 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index 118d2a2fd21..900981a64ed 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -7,55 +7,37 @@ author: Maria Jose Erquiaga, Splunk status: production type: TTP description: >- - The following analytic detects successful AWS Secrets Manager - BatchGetSecretValue requests from a principal that did not successfully use - this API during the previous seven days. It compares activity from the - latest hour with the preceding seven-day baseline. BatchGetSecretValue can - retrieve multiple secrets in one request, allowing an attacker using a - compromised AWS identity to collect credentials, tokens, passwords, and - other sensitive information while generating fewer CloudTrail events than - repeated GetSecretValue calls. + The following analytic detects an AWS principal making more than 10 + distinct successful Secrets Manager BatchGetSecretValue API calls within + one hour. It groups calls by AWS account and user identity ARN and + deduplicates CloudTrail events by event ID. Repeated batch retrieval may + indicate bulk access to secrets using a compromised identity, although + legitimate automation can produce similar activity. data_source: - AWS CloudTrail search: |- `cloudtrail` eventName="BatchGetSecretValue" eventSource="secretsmanager.amazonaws.com" - | spath input=_raw path=userIdentity.userName output=iam_user - | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.userName output=role_name - | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.arn output=issuer_arn - | spath input=_raw path=userIdentity.arn output=identity_arn - | spath input=_raw path=userIdentity.principalId output=principal_id - | spath input=_raw path=recipientAccountId output=vendor_account - | spath input=_raw path=sourceIPAddress output=src - | spath input=_raw path=userAgent output=user_agent - | spath input=_raw path=awsRegion output=vendor_region - | spath input=_raw path=errorCode output=error_code - | eval user=coalesce(issuer_arn, identity_arn, principal_id, iam_user, role_name, "unknown") - | where (isnull(error_code) OR error_code="") AND user!="unknown" - | addinfo - | eval evaluation_start=relative_time(info_max_time, "-1h"), baseline_start=relative_time(info_max_time, "-169h") - | where _time>=baseline_start AND _time<=info_max_time - | eval period=if(_time>=evaluation_start, "current", "baseline") - | stats count(eval(period="current")) as batch_request_count - count(eval(period="baseline")) as prior_batch_request_count - values(eval(if(period="current", src, null()))) as src - values(eval(if(period="current", user_agent, null()))) as user_agent - values(eval(if(period="current", vendor_account, null()))) as vendor_account - values(eval(if(period="current", vendor_region, null()))) as vendor_region - earliest(eval(if(period="current", _time, null()))) as firstTime - latest(eval(if(period="current", _time, null()))) as lastTime - by user - | where batch_request_count>0 AND prior_batch_request_count=0 + | eval user_arn='userIdentity.arn', vendor_account=recipientAccountId + | where isnotnull(user_arn) AND isnotnull(vendor_account) + AND (isnull(errorCode) OR errorCode="" OR lower(errorCode)="success") + | eval event_key=coalesce(eventID, md5(_raw)) + | dedup event_key + | eval event_time=_time + | bin _time span=1h + | stats count AS batch_request_count + values(sourceIPAddress) AS src + values(userAgent) AS user_agent + values(awsRegion) AS vendor_region + earliest(event_time) AS firstTime + latest(event_time) AS lastTime + BY _time vendor_account user_arn + | where batch_request_count>10 | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `aws_anomalous_secrets_manager_batch_retrieval_filter` how_to_implement: >- Ingest AWS CloudTrail management events into Splunk using the - aws:cloudtrail sourcetype. Configure the scheduled search to run hourly - with a 169-hour lookback. The analytic treats the latest hour as the - current evaluation period and the preceding 168 hours as the baseline. - Ensure the search has a finite latest-time boundary because the current - and baseline periods are derived from info_max_time. Adjust the scheduling - delay as necessary to account for CloudTrail ingestion latency. + aws:cloudtrail sourcetype. known_false_positives: Legitimate administrators, deployment systems, credential-rotation services, incident-response tooling, or newly deployed applications may use BatchGetSecretValue for the first time. A principal may trigger the detection again when it has not used the API during the preceding seven days. Review the principal, source IP address, user agent, account, region, requested secret identifiers, and surrounding CloudTrail activity to determine whether the request was authorized. references: - https://attack.mitre.org/techniques/T1555/006/ @@ -63,22 +45,22 @@ references: - https://docs.aws.amazon.com/secretsmanager/latest/userguide/cloudtrail_log_entries.html - https://www.elastic.co/guide/en/security/8.19/aws-secrets-manager-rapid-secrets-retrieval.html drilldown_searches: - - name: View the detection results for - "$user$" - search: '%original_detection_search% | search user="$user$"' + - name: View detection results for - "$user_arn$" + search: '%original_detection_search% | search user_arn="$user_arn$" vendor_account="$vendor_account$"' earliest_offset: $info_min_time$ latest_offset: $info_max_time$ - - name: View successful Secrets Manager activity for the last 7 days for - "$user$" - search: '`cloudtrail` eventSource="secretsmanager.amazonaws.com" | spath input=_raw path=userIdentity.userName output=iam_user | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.userName output=role_name | spath input=_raw path=userIdentity.sessionContext.sessionIssuer.arn output=issuer_arn | spath input=_raw path=userIdentity.arn output=identity_arn | spath input=_raw path=userIdentity.principalId output=principal_id | spath input=_raw path=errorCode output=error_code | eval user=coalesce(issuer_arn, identity_arn, principal_id, iam_user, role_name, "unknown") | where (isnull(error_code) OR error_code="") AND user="$user$" | table _time user eventName sourceIPAddress userAgent awsRegion requestParameters' + - name: View Secrets Manager activity for the last 7 days for - "$user_arn$" + search: '`cloudtrail` eventSource="secretsmanager.amazonaws.com" | eval user_arn=''userIdentity.arn'', vendor_account=recipientAccountId | where (isnull(errorCode) OR errorCode="" OR lower(errorCode)="success") | search user_arn="$user_arn$" vendor_account="$vendor_account$" | table _time user_arn vendor_account eventName sourceIPAddress userAgent awsRegion requestParameters' earliest_offset: 7d latest_offset: "0" - - name: View risk events for the last 7 days for - "$user$" - search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' + - name: View risk events for the last 7 days for - "$user_arn$" + search: '| from datamodel Risk.All_Risk | search normalized_risk_object="$user_arn$" | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' earliest_offset: 7d latest_offset: "0" finding: - title: User $user$ made $batch_request_count$ successful AWS Secrets Manager BatchGetSecretValue requests after no successful use of this API during the preceding seven days + title: Identity $user_arn$ made $batch_request_count$ successful AWS Secrets Manager BatchGetSecretValue requests within one hour entity: - field: user + field: user_arn type: user score: 40 threat_objects: From a87e87db66d1241d09dd6200eb472035780294c9 Mon Sep 17 00:00:00 2001 From: themaryjo Date: Fri, 9 Oct 2026 18:14:45 +0200 Subject: [PATCH 12/19] Update aws_repeated_cloudwatch_logs_read_operations.yml updating after review comments, using CIM fields --- ...peated_cloudwatch_logs_read_operations.yml | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml b/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml index 0dd66381bc2..cdd616922d7 100644 --- a/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml +++ b/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml @@ -32,36 +32,30 @@ search: |- recipientAccountId, 'userIdentity.accountId' ) - | eval role_name='userIdentity.sessionContext.sessionIssuer.userName' - | eval signature=coalesce(signature, eventName) - | eval src=coalesce(src, sourceIPAddress) - | eval user_agent=coalesce(user_agent, userAgent) - | eval vendor_region=coalesce(vendor_region, awsRegion) - | where isnotnull(user) AND len(trim(user)) > 0 + | where isnotnull(user_arn) AND isnotnull(vendor_account) + | eval event_key=coalesce(eventID, md5(_raw)) + | dedup event_key | sort 0 _time - | streamstats time_window=5m count AS event_count - by user vendor_account + | streamstats time_window=5m + count AS event_count + min(_time) AS window_start + BY user_arn vendor_account | where event_count > 500 - | stats - max(event_count) AS event_count - min(_time) AS firstTime + | stats max(event_count) AS event_count + min(window_start) AS firstTime max(_time) AS lastTime - values(signature) AS api_operations - values(role_name) AS role_name + values(eventName) AS api_operations values(src) AS src values(user_agent) AS user_agent values(vendor_region) AS vendor_region - by user vendor_account + BY user_arn vendor_account | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `aws_repeated_cloudwatch_logs_read_operations_filter` how_to_implement: >- The Splunk Add-on for AWS is required to collect AWS CloudTrail events. Configure CloudTrail to capture management Read events and ingest them with - the aws:cloudtrail sourcetype. The tested CloudWatch Logs operations are - recorded as management events, so a CloudWatch Logs data-event selector is - not required. The analytic uses a rolling five-minute window and counts - unique CloudTrail event identifiers to reduce duplicate-ingestion effects. + the aws:cloudtrail sourcetype. known_false_positives: >- Automated log analytics, SIEM pipelines, incident-response tooling, backup processes, and administrative troubleshooting may continuously read @@ -77,18 +71,18 @@ references: - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetLogEvents.html - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetQueryResults.html drilldown_searches: - - name: View CloudWatch Logs read operations for - "$user$" - search: '%original_detection_search% | search user="$user$" vendor_account="$vendor_account$"' + - name: View CloudWatch Logs read operations for - "$user_arn$" + search: '%original_detection_search% | search user_arn="$user_arn$" vendor_account="$vendor_account$"' earliest_offset: $info_min_time$ latest_offset: $info_max_time$ - - name: View risk events for the last 7 days for - "$user$" - search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' + - name: View risk events for the last 7 days for - "$user_arn$" + search: '| from datamodel Risk.All_Risk | search normalized_risk_object="$user_arn$" | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' earliest_offset: 7d latest_offset: "0" finding: - title: AWS principal $user$ performed $event_count$ CloudWatch Logs read operations + title: AWS principal $user_arn$ performed $event_count$ CloudWatch Logs read operations within five minutes entity: - field: user + field: user_arn type: user score: 50 threat_objects: From 67bfd5dff224c681ca152f2c06d9ff55451bde1f Mon Sep 17 00:00:00 2001 From: merquiag Date: Wed, 14 Oct 2026 18:42:55 +0200 Subject: [PATCH 13/19] Remove duplicate CloudWatch Logs detection from behavioral PR --- ...peated_cloudwatch_logs_read_operations.yml | 108 ------------------ 1 file changed, 108 deletions(-) delete mode 100644 detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml diff --git a/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml b/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml deleted file mode 100644 index cdd616922d7..00000000000 --- a/detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: AWS Repeated CloudWatch Logs Read Operations -id: 3f0ba9e1-c6cf-4317-a6bc-c4bcab2b5a06 -version: 1 -creation_date: '2026-09-21' -modification_date: '2026-09-21' -author: Maria Jose Erquiaga, Splunk -status: production -type: TTP -description: >- - The following analytic detects a high volume of Amazon CloudWatch Logs read - operations performed by the same AWS principal and account within a - five-minute period. It identifies more than 500 DescribeLogGroups, - DescribeLogStreams, DownloadLogEvents, FilterLogEvents, GetLogEvents, or - GetQueryResults API calls. This activity may indicate automated discovery or - collection of operational, application, or security telemetry from - CloudWatch Logs. -data_source: - - AWS CloudTrail -search: |- - `cloudtrail` eventSource="logs.amazonaws.com" - eventName IN ( - "DescribeLogGroups", - "DescribeLogStreams", - "DownloadLogEvents", - "FilterLogEvents", - "GetLogEvents", - "GetQueryResults" - ) - | eval user='userIdentity.principalId' - | eval vendor_account=coalesce( - vendor_account, - recipientAccountId, - 'userIdentity.accountId' - ) - | where isnotnull(user_arn) AND isnotnull(vendor_account) - | eval event_key=coalesce(eventID, md5(_raw)) - | dedup event_key - | sort 0 _time - | streamstats time_window=5m - count AS event_count - min(_time) AS window_start - BY user_arn vendor_account - | where event_count > 500 - | stats max(event_count) AS event_count - min(window_start) AS firstTime - max(_time) AS lastTime - values(eventName) AS api_operations - values(src) AS src - values(user_agent) AS user_agent - values(vendor_region) AS vendor_region - BY user_arn vendor_account - | `security_content_ctime(firstTime)` - | `security_content_ctime(lastTime)` - | `aws_repeated_cloudwatch_logs_read_operations_filter` -how_to_implement: >- - The Splunk Add-on for AWS is required to collect AWS CloudTrail events. - Configure CloudTrail to capture management Read events and ingest them with - the aws:cloudtrail sourcetype. -known_false_positives: >- - Automated log analytics, SIEM pipelines, incident-response tooling, backup - processes, and administrative troubleshooting may continuously read - CloudWatch Logs and generate a high volume of matching operations. The - prevalence of this activity has not yet been evaluated against broader - customer telemetry. Review the principal, account, source addresses, user - agents, Regions, and accessed CloudWatch Logs resources before escalating. -references: - - https://attack.mitre.org/techniques/T1530/ - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogGroups.html - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogStreams.html - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_FilterLogEvents.html - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetLogEvents.html - - https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetQueryResults.html -drilldown_searches: - - name: View CloudWatch Logs read operations for - "$user_arn$" - search: '%original_detection_search% | search user_arn="$user_arn$" vendor_account="$vendor_account$"' - earliest_offset: $info_min_time$ - latest_offset: $info_max_time$ - - name: View risk events for the last 7 days for - "$user_arn$" - search: '| from datamodel Risk.All_Risk | search normalized_risk_object="$user_arn$" | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`' - earliest_offset: 7d - latest_offset: "0" -finding: - title: AWS principal $user_arn$ performed $event_count$ CloudWatch Logs read operations within five minutes - entity: - field: user_arn - type: user - score: 50 -threat_objects: - - field: src - type: ip_address -analytic_story: - - Data Exfiltration -asset_type: AWS Account -mitre_attack_id: - - T1530 -product: - - Splunk Enterprise - - Splunk Enterprise Security - - Splunk Cloud -category: cloud -security_domain: threat -tests: - - name: True Positive Test - attack_data: - - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1530/aws_cloudwatch_logs_high_volume_retrieval/aws_cloudwatch_logs_high_volume_retrieval.json - sourcetype: aws:cloudtrail - source: aws_cloudtrail - test_type: unit From 3b7e9145165e633d5c77a80fadff7f3dd48189b1 Mon Sep 17 00:00:00 2001 From: themaryjo Date: Thu, 15 Oct 2026 17:13:57 +0200 Subject: [PATCH 14/19] Update aws_anomalous_secrets_manager_batch_retrieval.yml Fixing logic for time window one hour in total, and description of tests --- ...malous_secrets_manager_batch_retrieval.yml | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index 900981a64ed..eb0cfec006d 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -23,15 +23,18 @@ search: |- | eval event_key=coalesce(eventID, md5(_raw)) | dedup event_key | eval event_time=_time - | bin _time span=1h - | stats count AS batch_request_count + | sort 0 + _time + | streamstats time_window=1h count AS rolling_batch_request_count + min(event_time) AS window_start + BY vendor_account user_arn + | where rolling_batch_request_count>10 + | stats max(rolling_batch_request_count) AS batch_request_count values(sourceIPAddress) AS src values(userAgent) AS user_agent values(awsRegion) AS vendor_region - earliest(event_time) AS firstTime + earliest(window_start) AS firstTime latest(event_time) AS lastTime - BY _time vendor_account user_arn - | where batch_request_count>10 + BY vendor_account user_arn | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `aws_anomalous_secrets_manager_batch_retrieval_filter` @@ -84,9 +87,7 @@ tests: source: aws_cloudtrail sourcetype: aws:cloudtrail description: >- - This analytic requires a 169-hour search window so it can compare - activity in the latest hour with the preceding seven-day baseline. - The test data contains the current-period event but does not reproduce - the complete scheduled-search time context required for an automated - unit test. + Verifies that the analytic detects more than 10 successful + BatchGetSecretValue API calls by the same principal in the same AWS + account within a rolling one-hour window. test_type: experimental From 3dd3269e5b81184f502155282f425d88fcc3852e Mon Sep 17 00:00:00 2001 From: merquiag Date: Thu, 15 Oct 2026 19:05:47 +0200 Subject: [PATCH 15/19] Fix Secrets Manager batch retrieval detection validation --- .../cloud/aws_anomalous_secrets_manager_batch_retrieval.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index eb0cfec006d..99d1e96ed15 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -87,7 +87,7 @@ tests: source: aws_cloudtrail sourcetype: aws:cloudtrail description: >- - Verifies that the analytic detects more than 10 successful - BatchGetSecretValue API calls by the same principal in the same AWS - account within a rolling one-hour window. + Verifies that the analytic detects more than 10 successful + BatchGetSecretValue API calls by the same principal in the same AWS + account within a rolling one-hour window. test_type: experimental From a21b74f5a2c1e8abaa77e239e54dc73aeddedb8e Mon Sep 17 00:00:00 2001 From: themaryjo Date: Fri, 16 Oct 2026 18:41:33 +0200 Subject: [PATCH 16/19] Update aws_anomalous_secrets_manager_batch_retrieval.yml Replace fixed clock-hour buckets with a rolling one-hour count so bursts spanning an hour boundary can trigger the >10-request threshold. Retain all events in the latest qualifying window before aggregation so the count, timestamps, source IPs, user agents, and regions describe the same burst. --- ...aws_anomalous_secrets_manager_batch_retrieval.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index 99d1e96ed15..c92530edffe 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -27,12 +27,18 @@ search: |- | streamstats time_window=1h count AS rolling_batch_request_count min(event_time) AS window_start BY vendor_account user_arn - | where rolling_batch_request_count>10 - | stats max(rolling_batch_request_count) AS batch_request_count + | eventstats max(eval(if(rolling_batch_request_count>10, event_time, null()))) AS qualifying_end + BY vendor_account user_arn + | eventstats min(eval(if(event_time=qualifying_end, window_start, null()))) AS qualifying_start + BY vendor_account user_arn + | where isnotnull(qualifying_start) + AND event_time>=qualifying_start + AND event_time<=qualifying_end + | stats count AS batch_request_count values(sourceIPAddress) AS src values(userAgent) AS user_agent values(awsRegion) AS vendor_region - earliest(window_start) AS firstTime + earliest(event_time) AS firstTime latest(event_time) AS lastTime BY vendor_account user_arn | `security_content_ctime(firstTime)` From 909b30a68c69bd33500de62e7854a99214459fc2 Mon Sep 17 00:00:00 2001 From: nasbench <8741929+nasbench@users.noreply.github.com> Date: Mon, 19 Oct 2026 00:53:33 +0200 Subject: [PATCH 17/19] Update aws_anomalous_secrets_manager_batch_retrieval.yml --- ...malous_secrets_manager_batch_retrieval.yml | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index c92530edffe..ded21261751 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -7,40 +7,54 @@ author: Maria Jose Erquiaga, Splunk status: production type: TTP description: >- - The following analytic detects an AWS principal making more than 10 - distinct successful Secrets Manager BatchGetSecretValue API calls within - one hour. It groups calls by AWS account and user identity ARN and - deduplicates CloudTrail events by event ID. Repeated batch retrieval may - indicate bulk access to secrets using a compromised identity, although - legitimate automation can produce similar activity. + The following analytic detects an AWS principal making more than 10 distinct successful Secrets Manager BatchGetSecretValue API calls within + one hour. + It groups calls by AWS account and user identity ARN and deduplicates CloudTrail events by event ID. + Repeated batch retrieval may indicate bulk access to secrets using a compromised identity, although legitimate automation can produce similar activity. data_source: - AWS CloudTrail search: |- - `cloudtrail` eventName="BatchGetSecretValue" eventSource="secretsmanager.amazonaws.com" - | eval user_arn='userIdentity.arn', vendor_account=recipientAccountId - | where isnotnull(user_arn) AND isnotnull(vendor_account) - AND (isnull(errorCode) OR errorCode="" OR lower(errorCode)="success") + `cloudtrail` + eventName="BatchGetSecretValue" + eventSource="secretsmanager.amazonaws.com" + + | eval user_arn='userIdentity.arn', + vendor_account=recipientAccountId + + | where isnotnull(user_arn) + isnotnull(vendor_account) + (isnull(errorCode) OR errorCode="" OR lower(errorCode)="success") + | eval event_key=coalesce(eventID, md5(_raw)) + | dedup event_key + | eval event_time=_time + | sort 0 + _time + | streamstats time_window=1h count AS rolling_batch_request_count min(event_time) AS window_start BY vendor_account user_arn + | eventstats max(eval(if(rolling_batch_request_count>10, event_time, null()))) AS qualifying_end BY vendor_account user_arn + | eventstats min(eval(if(event_time=qualifying_end, window_start, null()))) AS qualifying_start BY vendor_account user_arn + | where isnotnull(qualifying_start) - AND event_time>=qualifying_start - AND event_time<=qualifying_end + event_time>=qualifying_start + event_time<=qualifying_end + | stats count AS batch_request_count - values(sourceIPAddress) AS src - values(userAgent) AS user_agent - values(awsRegion) AS vendor_region - earliest(event_time) AS firstTime - latest(event_time) AS lastTime - BY vendor_account user_arn + values(sourceIPAddress) AS src + values(userAgent) AS user_agent + values(awsRegion) AS vendor_region + earliest(event_time) AS firstTime + latest(event_time) AS lastTime + BY vendor_account user_arn + | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `aws_anomalous_secrets_manager_batch_retrieval_filter` @@ -67,7 +81,7 @@ drilldown_searches: earliest_offset: 7d latest_offset: "0" finding: - title: Identity $user_arn$ made $batch_request_count$ successful AWS Secrets Manager BatchGetSecretValue requests within one hour + title: The identity [$user_arn$] made [$batch_request_count$] successful AWS Secrets Manager BatchGetSecretValue requests within one hour entity: field: user_arn type: user @@ -92,8 +106,4 @@ tests: - data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1555.006/aws_anomalous_secrets_manager_batch_retrieval/aws_anomalous_secrets_manager_batch_retrieval.json source: aws_cloudtrail sourcetype: aws:cloudtrail - description: >- - Verifies that the analytic detects more than 10 successful - BatchGetSecretValue API calls by the same principal in the same AWS - account within a rolling one-hour window. - test_type: experimental + test_type: unit From 2d507c2df35409c5c7dea97624c61eb45fd1a37f Mon Sep 17 00:00:00 2001 From: nasbench <8741929+nasbench@users.noreply.github.com> Date: Mon, 19 Oct 2026 01:23:31 +0200 Subject: [PATCH 18/19] Update aws_anomalous_secrets_manager_batch_retrieval.yml --- .../aws_anomalous_secrets_manager_batch_retrieval.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index ded21261751..195d75be93a 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -21,9 +21,8 @@ search: |- | eval user_arn='userIdentity.arn', vendor_account=recipientAccountId - | where isnotnull(user_arn) - isnotnull(vendor_account) - (isnull(errorCode) OR errorCode="" OR lower(errorCode)="success") + | where isnotnull(user_arn) AND isnotnull(vendor_account) + AND (isnull(errorCode) OR errorCode="" OR lower(errorCode)="success") | eval event_key=coalesce(eventID, md5(_raw)) @@ -44,8 +43,8 @@ search: |- BY vendor_account user_arn | where isnotnull(qualifying_start) - event_time>=qualifying_start - event_time<=qualifying_end + AND event_time>=qualifying_start + AND event_time<=qualifying_end | stats count AS batch_request_count values(sourceIPAddress) AS src @@ -53,6 +52,7 @@ search: |- values(awsRegion) AS vendor_region earliest(event_time) AS firstTime latest(event_time) AS lastTime + BY vendor_account user_arn | `security_content_ctime(firstTime)` From 5f906f1ab6b7d5c9a97b8514665ec4a37332d8db Mon Sep 17 00:00:00 2001 From: Nasreddine Bencherchali Date: Mon, 19 Oct 2026 02:01:47 +0200 Subject: [PATCH 19/19] Update aws_anomalous_secrets_manager_batch_retrieval.yml --- .../aws_anomalous_secrets_manager_batch_retrieval.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml index 195d75be93a..9243a8a30d0 100644 --- a/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml +++ b/detections/cloud/aws_anomalous_secrets_manager_batch_retrieval.yml @@ -58,10 +58,12 @@ search: |- | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)` | `aws_anomalous_secrets_manager_batch_retrieval_filter` -how_to_implement: >- - Ingest AWS CloudTrail management events into Splunk using the - aws:cloudtrail sourcetype. -known_false_positives: Legitimate administrators, deployment systems, credential-rotation services, incident-response tooling, or newly deployed applications may use BatchGetSecretValue for the first time. A principal may trigger the detection again when it has not used the API during the preceding seven days. Review the principal, source IP address, user agent, account, region, requested secret identifiers, and surrounding CloudTrail activity to determine whether the request was authorized. +how_to_implement: |- + Ingest AWS CloudTrail management events into Splunk using the aws:cloudtrail sourcetype. +known_false_positives: |- + Legitimate administrators, deployment systems, credential-rotation services, incident-response tooling, or newly deployed applications may use BatchGetSecretValue for the first time. + A principal may trigger the detection again when it has not used the API during the preceding seven days. + Review the principal, source IP address, user agent, account, region, requested secret identifiers, and surrounding CloudTrail activity to determine whether the request was authorized. references: - https://attack.mitre.org/techniques/T1555/006/ - https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_BatchGetSecretValue.html