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 0000000000..158048b69e --- /dev/null +++ b/detections/cloud/aws_ec2_multiple_ssh_public_keys_uploaded_by_a_single_identity.yml @@ -0,0 +1,73 @@ +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 + | stats dc(eventID) 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_arn + | 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: You must install Splunk AWS Add on and enable CloudTrail logs in your AWS Environment. +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_arn$ uploaded $event_count$ SSH public keys using EC2 Instance Connect + entity: + field: user_arn + 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 diff --git a/detections/cloud/aws_ec2_startup_script_modified_on_stopped_instance.yml b/detections/cloud/aws_ec2_startup_script_modified_on_stopped_instance.yml new file mode 100644 index 0000000000..b93074de64 --- /dev/null +++ b/detections/cloud/aws_ec2_startup_script_modified_on_stopped_instance.yml @@ -0,0 +1,75 @@ +name: AWS EC2 Startup Script Modified on Stopped Instance +id: 84578867-02bf-475e-9815-6e8e0d798253 +version: 1 +creation_date: '2026-10-07' +modification_date: '2026-10-07' +author: Maria Jose Erquiaga +status: production +type: TTP +description: >- + A successful EC2 StopInstances request was followed within one hour by a + successful ModifyInstanceAttribute request targeting userData on the same + instance, by the same IAM principal. This may indicate an attempt to change + instance startup behavior or establish persistence. These CloudTrail events + do not confirm that the modified user-data script executed. +data_source: + - AWS CloudTrail +search: |- + `cloudtrail` eventSource="ec2.amazonaws.com" eventName IN ("StopInstances", "ModifyInstanceAttribute") + | eval user_arn='userIdentity.arn' + | eval instance_id=case( + eventName="StopInstances", 'requestParameters.instancesSet.items{}.instanceId', + eventName="ModifyInstanceAttribute", 'requestParameters.instanceId' + ) + | mvexpand instance_id + | where isnotnull(eventID) AND isnotnull(instance_id) AND isnotnull(user_arn) + AND (isnull(errorCode) OR errorCode="" OR lower(errorCode)="success") + | where eventName="StopInstances" + OR (eventName="ModifyInstanceAttribute" AND 'requestParameters.attribute'="userData") + | dedup eventID instance_id + | eval event_epoch=coalesce(strptime(eventTime, "%Y-%m-%dT%H:%M:%SZ"), _time) + | sort 0 user_arn instance_id awsRegion event_epoch + | streamstats current=f + last(eval(if(eventName="StopInstances", event_epoch, null()))) AS stop_epoch + last(eval(if(eventName="StopInstances", eventID, null()))) AS stop_event_id + BY user_arn instance_id awsRegion + | where eventName="ModifyInstanceAttribute" AND isnotnull(stop_epoch) + | eval elapsed_seconds=event_epoch-stop_epoch + | where elapsed_seconds>0 AND elapsed_seconds<=3600 + | eval stop_time_utc=strftime(stop_epoch, "%Y-%m-%dT%H:%M:%SZ") + | eval modification_time_utc=strftime(event_epoch, "%Y-%m-%dT%H:%M:%SZ") + | rename eventID AS modification_event_id + | table user_arn instance_id awsRegion stop_time_utc modification_time_utc elapsed_seconds stop_event_id modification_event_id + | `aws_ec2_startup_script_modified_on_stopped_instance_filter` +how_to_implement: You must install Splunk AWS Add on and enable CloudTrail logs in your AWS Environment. +known_false_positives: >- + Authorized administrators and infrastructure automation may stop instances + and update their user data. Review the IAM principal, instance, change + window, and approved automation before escalating. +references: + - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html + - https://stratus-red-team.cloud/attack-techniques/AWS/aws.execution.ec2-user-data/ +finding: + title: IAM principal $user_arn$ modified user data on stopped EC2 instance $instance_id$ + entity: + field: user_arn + type: user + score: 50 +analytic_story: + - Suspicious Cloud User Activities +asset_type: AWS Account +mitre_attack_id: + - T1578.005 +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/T1578.005/aws_ec2_startup_script_modification/aws_ec2_startup_script_modification.json + sourcetype: aws:cloudtrail + source: aws_cloudtrail + test_type: unit diff --git a/detections/cloud/aws_iam_role_created_with_external_trust.yml b/detections/cloud/aws_iam_role_created_with_external_trust.yml new file mode 100644 index 0000000000..c4ade2099d --- /dev/null +++ b/detections/cloud/aws_iam_role_created_with_external_trust.yml @@ -0,0 +1,99 @@ +name: AWS IAM Role Created with External Trust +id: 6b7f646b-5ddc-48c9-baa3-fd66ac202c18 +version: 1 +creation_date: '2026-10-13' +modification_date: '2026-10-13' +author: Maria Jose Erquiaga, Splunk +status: production +type: TTP +description: >- + Detects a successful AWS IAM CreateRole event whose trust policy allows + sts:AssumeRole by a principal in another AWS account or by a wildcard + principal. This configuration may provide persistent access if the + principal can assume the role. The event alone does not establish that + the role was assumed or that its creation was unauthorized. +data_source: + - AWS CloudTrail +search: |- + `cloudtrail` eventSource="iam.amazonaws.com" eventName="CreateRole" + | where isnull(errorCode) OR errorCode="" OR lower(errorCode)="success" + | eval policy_raw='requestParameters.assumeRolePolicyDocument', + role_name='requestParameters.roleName', + role_arn='responseElements.role.arn', + user_arn=coalesce(user_arn, 'userIdentity.arn'), + src=coalesce(src, sourceIPAddress) + | where isnotnull(policy_raw) + | eval policy=if(match(policy_raw, "(?i)^%7b"), urldecode(policy_raw), policy_raw) + | rex field=role_arn "^arn:[^:]+:iam::(?[0-9]{12}):role/" + | eval role_account_id=coalesce(account_from_role_arn, recipientAccountId) + | eval statements_json=json_extract(policy, "Statement") + | eval statements=if(match(statements_json, "^\s*\["), json_array_to_mv(statements_json), statements_json) + | mvexpand statements + | eval effect=json_extract(statements, "Effect"), + actions_json=json_extract(statements, "Action"), + principal_json=json_extract(statements, "Principal") + | eval actions=if(match(actions_json, "^\s*\["), json_array_to_mv(actions_json, true()), actions_json) + | mvexpand actions + | mvexpand actions + | eval trust_action=actions + | eval action_pattern=replace(replace(lower(trust_action), "[*]", "%"), "[?]", "_") + | where effect="Allow" AND like("sts:assumerole", action_pattern) + | eval aws_json=json_extract(principal_json, "AWS") + | eval principals=case( + match(aws_json, "^\s*\["), json_array_to_mv(aws_json, true()), + isnotnull(aws_json), aws_json, + principal_json="*", "*" + ) + | mvexpand principals + | eval trust_principal=principals + | rex field=trust_principal "^arn:[^:]+:iam::(?[0-9]{12}):" + | eval principal_account_id=if( + match(trust_principal, "^[0-9]{12}$"), + trust_principal, + principal_account_from_arn + ) + | eval trust_type=case( + trust_principal="*", "wildcard", + isnotnull(role_account_id) AND isnotnull(principal_account_id) + AND principal_account_id!=role_account_id, "external" + ) + | where trust_type="wildcard" OR trust_type="external" + | eval event_key=coalesce(eventID, md5(_raw)) + | dedup event_key trust_principal + | table _time eventID user_arn src role_name role_arn role_account_id trust_type trust_principal principal_account_id trust_action + | `aws_iam_role_created_with_external_trust_filter` +how_to_implement: You must install Splunk AWS Add on and enable CloudTrail logs in your AWS Environment. +known_false_positives: >- + Approved cross-account integrations and deliberately configured wildcard + trust may match. Review the role owner, trust-policy conditions, role + permissions, and whether the principal is expected. +references: + - https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateRole.html + - https://stratus-red-team.cloud/attack-techniques/aws/aws.persistence.iam-backdoor-role/ +finding: + title: AWS IAM role $role_name$ created with $trust_type$ trust + entity: + field: user_arn + type: user + score: 40 +threat_objects: + - field: src + type: ip_address +analytic_story: + - AWS IAM Privilege Escalation +asset_type: AWS Account +mitre_attack_id: + - T1098.003 +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/T1098.003/aws_iam_role_created_with_external_trust/aws_iam_role_created_with_external_trust.json + sourcetype: aws:cloudtrail + source: aws_cloudtrail + test_type: unit