Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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 ConnectSendSSHPublicKey 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('requestParameters.instanceId') as unique_instances
dc(eventID) as event_count
earliest(_time) as firstTime
latest(_time) as lastTime
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
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: The 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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: The IAM principal [$user_arn$] modified user data on stopped EC2 instance [$instance_id$]
entity:
field: user_arn
type: user
score: 50
threat_objects: []
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
122 changes: 122 additions & 0 deletions detections/cloud/aws_iam_role_created_with_external_trust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
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::(?<account_from_role_arn>[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::(?<principal_account_from_arn>[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: The AWS IAM role [$role_name$] created with [$trust_type$] trust
entity:
field: user_arn
type: user
score: 50
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
Loading