When performing cloud penetration tests (CPTs), the goal is to find and exploit high-severity issues such as privilege escalation, unintended code execution, or the ability to pivot into a production environment. We often start as a low-privileged user in a development environment with all the access and permissions typically assigned to a new hire. During some engagements, the escalation path takes us from this low-privileged user to administrator all within the same AWS account. But what happens if the escalation path involves leaving the organization and assuming an external role? Let's look at how we can utilize the AWS CloudTrail service to discover other AWS accounts that we could pivot to.
Escalating Privileges
In our experience, imitating a new developer is the most common pretext when doing a CPT. The reason we ask for ALL the access and permissions usually associated with a new hire is because secrets are stored in many places and by limiting scope only to AWS assets, we would miss viable privilege escalation paths available to company employees.
The developer may have generous permissions in the development environment – although usually shy of admin – but they certainly should not be able to access or modify assets in the production environment. From this starting position, we’ll typically enumerate the development environment as much as possible and try to escalate privileges by finding API keys. We usually find some, whether in S3 buckets, Lambda environment variables, an internal wiki, or stored elsewhere such as GitHub or BitBucket code repositories.
Once we find one or more sets of AWS API keys, the question is what they have access to and what is their possible use. Figuring out access and permissions in the development environment is easy enough; we’ve already enumerated it, so we can simply look that up. But what can we do with those keys in other AWS accounts? Some of that might be easy to figure out depending on how permission policies are formulated. For example, the policy below shows that principals can take on a role in AWS account 123456789012
, assuming of course that the role’s trust policy also allows it:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "Example1", "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::123456789012:role/myassumerole" } ] }
But what if the API keys belong to an account that has the following permissions policy attached to it?
{ "Version": "2012-10-17", "Statement": [ { "Sid": "Example2", "Effect": "Allow", "Action": "sts:*", "Resource": "*" } ] }
This user account may be able to assume roles in multiple AWS accounts, but nothing in the permissions policy provides information about AWS account IDs or role names. You have to know these details to assume the roles. AWS does not have a mechanism through which a user can query all the roles in other accounts that can be assumed (which is a good thing), so during a CPT, it would be impossible to figure out that we can pivot into AWS account 123456789012
, right? Or maybe not.
An existing tool that can be used to enumerate roles in AWS environments is Pacu, specifically the iam__enum_roles module. This module will attempt to guess role names in a target account and determine if they can be assumed. But using this Pacu module still requires us to know the target AWS account ID that we want to pivot to. What if we don’t have that information? Fortunately, there’s another method to find roles in other AWS accounts that can be used in conjunction with existing tools and techniques to enumerate privilege escalation paths.
Assuming Roles
Let’s say that we found a valid set of API keys for a user account named deployBot. During enumeration, we find that this user account has limited permissions in the current AWS account but that it has a policy attached to it with sts:assumeRole:*
. Given the account name, we suspect that we may be able to use it to pivot into another environment, but first, we need to know which account ID and role name to target.
While we may not be able to exhaustively enumerate all the roles that we can assume in other AWS accounts, if our user has the cloudtrail:LookupEvents
permission, we may be able to use CloudTrail to try and enumerate some of those roles. In AWS, assuming a role is a CloudTrail management event, details will be stored in any trails that are configured to capture such management events. Therefore, we can query CloudTrail and search for any successful assumeRole
events to try and find other AWS accounts we can access that we did not yet know about. The following one-liner does exactly that:
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole --region us-east-1 | jq '.Events | .[] | .Username as $username | .CloudTrailEvent | fromjson | {timestamp: .eventTime, user: $username, assumedRole: .requestParameters.roleArn}'
The output from this command looks something like this:
{ "timestamp": "2021-12-23T00:35:45Z", "user": "root", "assumedRole": null } { "timestamp": "2021-12-23T00:00:26Z", "user": "user1", "assumedRole": "arn:aws:iam::123456789012:role/SecurityAudit" } { "timestamp": "2021-12-23T00:00:26Z", "user": "user1", "assumedRole": "arn:aws:iam::210987654321:role/SecurityAudit" } { "timestamp": "2021-12-22T23:50:41Z", "user": "deploybot", "assumedRole": "arn:aws:iam::112233445566:role/deployToProd" }
From this output, we can see that someone tried to use the root account to assume a role, but this is disallowed by AWS, so it failed. We also see that user user1
assumed roles in AWS accounts 123456789012
and 210987654321
. However, the more interesting entry is for our user deploybot
, who assumed a role named deployToProd
in AWS Account 112233445566
. That certainly seems like a thread worth following.
We verify with the client that this account belongs to them and that we have approval to pursue this lead before doing so, and after confirmation, we assume the role and start exploring the new AWS account. Where things go from there depends on the permissions assigned to the deploybot
account and assets in AWS account 112233445566
. The user account could have almost no permissions in which case this path proves to be a dead end. Or, the user account could have full administrative permissions, in which case we just successfully compromised the client's production account. Or anything in between. More often than not though, with some in-between privilege escalation steps, we eventually reach our goal of compromising critical production resources. The point is that CloudTrail can be used to discover the existence of such privilege escalation paths in the first place.
Conclusion
The scenario outlined above is a simplified but realistic cloud penetration testing situation that we often encounter. During enumeration, we often find credentials or API keys leading to AWS accounts that the client did not tell us about but provide a viable privilege escalation path to sensitive cloud resources. The one-liner described in this blog post introduces a novel way to approach enumeration of the AWS attack surface by using information from CloudTrail to list assumeRole
events. This could reveal existing cross-account relationships that might go unnoticed otherwise, especially if unusual role names are involved that would be missed by tools that guess or bruteforce role names. Our hope is that this information will help other cloud penetration testers identify and fix cross-account access issues.
For more information on privilege escalation within AWS, check out these resources:
Subscribe to Bishop Fox's Security Blog
Be first to learn about latest tools, advisories, and findings.
Thank You! You have been subscribed.