| ID | Technique | Tactic |
|---|---|---|
| T1098.003 | Additional Cloud Roles | Persistence |
Detection: AWS IAM Role Created with External Trust
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.
Search
1`cloudtrail`
2eventSource="iam.amazonaws.com"
3eventName="CreateRole"
4
5
6| where isnull(errorCode) OR errorCode="" OR lower(errorCode)="success"
7
8
9| eval policy_raw='requestParameters.assumeRolePolicyDocument',
10 role_name='requestParameters.roleName',
11 role_arn='responseElements.role.arn',
12 user_arn=coalesce(user_arn, 'userIdentity.arn'),
13 src=coalesce(src, sourceIPAddress)
14
15
16| where isnotnull(policy_raw)
17
18
19| eval policy=if(match(policy_raw, "(?i)^%7b"), urldecode(policy_raw), policy_raw)
20
21
22| rex field=role_arn "^arn:[^:]+:iam::(?<account_from_role_arn>[0-9]{12}):role/"
23
24
25| eval role_account_id=coalesce(account_from_role_arn, recipientAccountId)
26
27| eval statements_json=json_extract(policy, "Statement")
28
29| eval statements=if(match(statements_json, "^\s*\["), json_array_to_mv(statements_json), statements_json)
30
31
32| mvexpand statements
33
34
35| eval effect=json_extract(statements, "Effect"),
36 actions_json=json_extract(statements, "Action"),
37 principal_json=json_extract(statements, "Principal")
38
39
40| eval actions=if(match(actions_json, "^\s*\["), json_array_to_mv(actions_json), actions_json)
41
42
43| mvexpand actions
44
45| mvexpand actions
46
47
48| eval trust_action=actions
49
50| eval action_pattern=replace(replace(lower(trust_action), "[*]", "%"), "[?]", "_")
51
52
53| where effect="Allow" AND like("sts:assumerole", action_pattern)
54
55| eval aws_json=json_extract(principal_json, "AWS")
56
57
58| eval principals=case(
59 match(aws_json, "^\s*\["), json_array_to_mv(aws_json),
60 isnotnull(aws_json), aws_json,
61 principal_json="*", "*"
62 )
63
64
65| mvexpand principals
66
67
68| eval trust_principal=principals
69
70
71| rex field=trust_principal "^arn:[^:]+:iam::(?<principal_account_from_arn>[0-9]{12}):"
72
73
74| eval principal_account_id=if(
75 match(trust_principal, "^[0-9]{12}$"),
76 trust_principal,
77 principal_account_from_arn
78 )
79
80
81| eval trust_type=case(
82 trust_principal="*", "wildcard",
83 isnotnull(role_account_id) AND isnotnull(principal_account_id)
84 AND principal_account_id!=role_account_id, "external"
85 )
86
87
88| where trust_type="wildcard" OR trust_type="external"
89
90
91| eval event_key=coalesce(eventID, md5(_raw))
92
93
94| dedup event_key trust_principal
95
96
97| table _time eventID user_arn src role_name role_arn role_account_id
98 trust_type trust_principal principal_account_id trust_action
99
100
101| `aws_iam_role_created_with_external_trust_filter`
Data Source
| Name | Platform | Sourcetype | Source |
|---|---|---|---|
| AWS CloudTrail | 'aws:cloudtrail' |
'aws_cloudtrail' |
Macros Used
| Name | Value |
|---|---|
| cloudtrail | sourcetype=aws:cloudtrail |
| aws_iam_role_created_with_external_trust_filter | search * |
aws_iam_role_created_with_external_trust_filter is an empty macro by default. It allows the user to filter out any results (false positives) without editing the SPL.
Annotations
Default Configuration
This detection is configured by default in Splunk Enterprise Security to run with the following settings:
| Setting | Value |
|---|---|
| Disabled | true |
| Cron Schedule | 0 * * * * |
| Earliest Time | -70m@m |
| Latest Time | -10m@m |
| Schedule Window | auto |
| Creates Finding (Notable) | Yes |
| Rule Title | %name% |
| Rule Description | %description% |
| Notable Event Fields | user, dest |
| Creates Intermediate Finding (Risk Event) | No |
Implementation
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.
Associated Analytic Story
Finding
| Title | Entity Field | Entity Type | Risk Score |
|---|---|---|---|
| The AWS IAM role [$role_name$] created with [$trust_type$] trust | user_arn | user | 50 |
Threat Objects
| Field | Type |
|---|---|
| src | ip_address |
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/
Detection Testing
| Test Type | Status | Dataset | Source | Sourcetype |
|---|---|---|---|---|
| Validation | ✅ Passing | N/A | N/A | N/A |
| Unit | ✅ Passing | Dataset | aws_cloudtrail |
aws:cloudtrail |
| Integration | ✅ Passing | Dataset | aws_cloudtrail |
aws:cloudtrail |
Replay any dataset to Splunk Enterprise by using our replay.py tool or the UI.
Alternatively you can replay a dataset into a Splunk Attack Range
Source: GitHub |
Version: 1