As we have discussed previously, adversaries who gain access to AWS accounts can abuse the Security Token Service (STS) to grant themselves short-term tokens and assume roles before performing malicious activity within that cloud environment. Historically, the main way to attribute activity performed by a user after they have assumed a role was to track access keys every time a user assumed a new role. While this can work, there are also known workarounds to obfuscate activity and break the trail of access keys, making it difficult to trace activity back to the compromised user account.
However, AWS has an attribute in STS called SourceIdentity
that allows defenders to trace all user activity in AssumeRole
sessions back to corporate identities such as usernames or email addresses.
What is SourceIdentity
?
So, what is this SourceIdentity
attribute and what does it do for you? It allows you to require a field whenever a user assumes a role in AWS that is akin to role session name or session tags. SourceIdentity
stands apart from these other fields by the fact that it persists throughout all follow-on sessions the user creates. If they were to assume one role and then pivot to another, SourceIdentity
would persist in the logs even if they didn’t specify it in the subsequent AssumeRole
calls.
As with most fields in AWS, you can also specify SourceIdentity
to be a specific value. When you configure the trust relationships for the roles, you can specify that SourceIdentity
must be a username, email address, or any other identifier. You can also integrate it with your identity providers such as Okta, Ping, or OneLogin.
SourceIdentity
stands apart from other AWS attributes by the fact that it persists throughout all follow-on sessions the user creates.
SourceIdentity
in action
For this exercise, we will follow the user accessKeyIdTesting
through twoAssumeRole
calls and finally their execution of the GetCallerIdentity
API event. The screenshot below outlines the basic steps of the process.
So what does that look like in the logs?
First AssumeRole
event
aws sts assume-role --role-arn arn:aws:iam::<accountId>:role/accessKeyIdTesting --role-session-name sourceTesting --source-identity followMe --profile accessKeyTesting
The first event is the original AssumeRole
, wherein we specify the SourceIdentity
field as followMe
. Normally you would want to set the condition that this field is some sort of corporate identity such as an email address or username.
Since we specified the SourceIdentity
in the AssumeRole
request, we can observe it in both the responseElements
and the requestParameters
. Since this is the original AssumeRole
event, it is not present in the userIdentity
field (keep this in mind for later).
Figure 1: AssumeRole
event with SourceIdentity
In Figure 1 we can see that the new SourceIdentity
field is present in both the requestParameters
and the responseElements
. The responseElements
section should be the only one used to track SourceIdentity
as it will always be present even if it is not part of the original request, as will be evident in the next event.
AssumeRole
chain
aws sts assume-role --role-arn arn:aws:iam::<accountId>:role/lambdaManager --role-session-name sourceTesting --profile sourceIdenRole
The user then pivots to another role from the first one without specifying SourceIdentity
. In the logs, even though it wasn’t specified, it still shows up in the responseElements
! This makes it so much easier to track what the user is doing during their entire session. Previously, you’d have to track the access keys and capture that for each pivot to the new role.
Figure 2: AssumeRole userIdentity
elements
In Figure 2 above we can see that the SourceIdentity
field is present under the userIdentity.sessionContext
field and matches the value from the responseElement
in Figure 1. This allows us to trace the activity of the two events.
Figure 3: AssumeRole
part deux responseElements
In Figure 3 we can see that with this new AssumeRole
call, the SourceIdentity
was not specified in the command and therefore not included in the requestParameters
. However, since it was included in the first call it is still supplied in the responseElements
.
The API call
Finally, the user then in this final role executes the GetCallerIdentity
command. In the CloudTrail event we can clearly see the SourceIdentity
field in userIdentity
parameters. We can trace this back to that specific user regardless of what roles they have assumed or what events they execute.
Figure 4: GetCallerIdentity
As was true with the second AssumeRole
call, the SourceIdentity
field exists in the userIdentity.sessionContext
field. This allows us to trace back this activity from the API call all the way back to the AssumeRole
call without having to rely on access keys. It will also allow us to just directly tie this API call to whichever user identifier we choose when we configure SourceIdentity
to be required for all users.
So what?
Why is this attribute so helpful? Users may leverage several different roles while using services in AWS, and tracking who is doing what can be near impossible with medium-to-large AWS accounts. SourceIdentity
allows defenders to baseline “normal” activity more easily. We don’t have to rely on temporary and random credential identifiers to track sessions anymore; SourceIdentity
takes care of that for us.
With SourceIdentity
we can simply compare anomalous behavior to previous sessions to make a determination if it is malicious or not. Determining what constitutes “normal” behavior is almost impossible in cloud environments unless admins have strict security policies and well named, very descriptive roles. We know that this will not always be the case, and enabling SourceIdentity
will enable defenders to make determinations with significantly more confidence.