Abusing AWS cross-account relationships

Xavi Méndez
Edge-Security
Published in
5 min readJun 7, 2018

Introduction

Many Amazon Web Services (AWS) deployments use separate accounts for development, production and other environments. This helps to isolate resources and Identity and Access Management (IAM) users, groups, and roles.

To simplify management and the need to have a different set of credentials for every different environment, Amazon provides AWS Security Token Service (STS) and IAM roles. Cross-account access can be achieved by assuming a role and getting temporary credentials for authentication and authorization.

A typical scenario is depicted in the diagram below (see [1]):

A trust relationship is bidirectional, to set up the scenario above, the following must be created:

  • In prod, creating the CrossAccountPowerUser role with a trust relationship with the dev user:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::Dev-account-ID:user/bjwagner"
},
"Action": "sts:AssumeRole"
}]
}
  • In dev, adding a policy to the bjwagner user that allows to assume the prod role:
{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::Prod-account-ID:role/CrossAccountPowerUser"
}
}

The principal in the production role dictates who can assume it. Due to the fact that you cannot specify groups as principals but only a list of roles, users or services (see [2]), for convenience, it is quite common to use a whole account in the trust relationship, for example:

"Principal": { "AWS": "arn:aws:iam::123456789012:root" }

It is worth noting that, that this would allow any dev account that has the companion assume role policy to assume the role.

The following specifics of the depicted scenario affect its security posture:

  • The development environment is used to store IAM accounts (including administrators).
  • Usually, engineers have the ability to deploy resources, ie. ECS services or Cloud Formation stacks, in the development environment.
  • Usually, the development environments have wider and more relaxed permissions.
  • Usually, all the engineers will share a common role or group in development configured with default permissions.
  • Usually, trust relationships use other account as principal.

Many times, the default developer access has policies to prevent escalating privileges, such as, only allowing to assume roles in the same development account or specifically denying to assume an administrator role. For example:

{
"Sid": "DenyAssumeRolesThatAreNotDevelopment",
"Effect": "Deny",
"Action": "sts:AssumeRole",
"NotResource": [
"arn:aws:iam::123456789012:role/*",
]
},
{
"Sid": "ExplicitDenyAdminAssumeRole",
"Effect": "Deny",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::123456789012:role/Development_admins"
},

How to escalate privileges

Even with the aforementioned restrictions, there are still several vectors that can be exploited to escalate privileges depending on the existing IAM resources and the specific configuration of the environment.

A security assessment identifying these vectors should check trust relationships, roles, user and groups policies. Although, some of it might be doable using the AWS console or the AWS cli, it could be tedious. In order, to facilitate this task the following python script has been developed during this research https://github.com/xmendez/awsdigger

Dangerous permissions

There are various IAM permissions that could be used to escalate privileges, for example allowing to:

  • Create or modify users and group membership such as AddUserToGroup, PutUserPolicy, CreateUser, CreateGroup, PutGroupPolicy.
  • Create or modify managed policies such as AttachUserPolicy, AttachGroupPolicy, CreatePolicy.
  • Create or update other user access keys (you might need also MFA permissions if two factor authentication is enabled) with CreateAccessKey.
  • Create or modify existing roles such as createRole or PutRolePolicy.
  • Assume other roles with sts:assumeRole
  • Modify other roles trust policies with UpdateAssumeRolePolicy

A list of the full IAM permissions could be found at [4].

The obvious dangerous permissions, such as creating users or modifying existing groups, are usually denied by default. But many times development environments are quite relaxed about modifying other existing roles.

Also, often a developer is allowed to perform these actions indirectly, for example:

  • by deploying CF stacks (sometimes passing a more powerful role).
  • by deploying an ECS service or an EC2 instance allowed to assume a previously created role.

Finding IAM resources with overprivileged permissions

IAM resources with strong permissions could already exist with all the necessary permissions to escalate privileges in the development environment:

$ python -m awsdigger --filter "Action='iam:*' and Resource='*' and Condition=''"

An issue when finding such IAM resources, is that AWS policies’ syntax is very flexible and therefore it is difficult to identify the actual permissions among all the “NotAction”, “Action”, “Deny”, “Allow”, different services, resources, conditions, actions and wildcards.

For example, the following user policy, which is trying to give full s3 access, is actually granting administrator permissions to the user (presumably by mistake):

"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:*"
],
"Resource": [
"*"
],
"Effect": "Allow"
},
{
"Resource": [
"*"
],
"Effect": "Allow",
"NotAction": [
"s3:DeleteObject",
"s3:DeleteObjectVersion"
]
}
]
}

AWSDigger has a basic policy calculator that allows to find the effective permissions of an IAM policy to facilitate this task. Therefore you could look for dangerous IAM permissions by executing:

$ python -m awsdigger --filter "Effective~'iam:PutUserPolicy' or Effective~'CreateGroup' or Effective~'CreateUser' or Effective~'UpdateGroup' or Effective~'AttachGroupPolicy' "

Finding trust relationships within the same account

A role within the dev account having the development account as principal in the trust policy could be assumed by any user in the development account (if the default access allows it). These IAM resources can be identified by executing the following using a development account:

$ python -m awsdigger --filter "Trusted~'arn:aws:iam::123456789012:root'"

The listed roles might have wider permissions, access to more resources or a trust relationships to other account.

Then the role can be assumed by executing:

$ aws sts assume-role --role-arn arn:aws:iam::123456789012:role/some_role --role-session-name escalate-to-other-role

Finding cross-account trusted relationships

In order to allow developers to access their resources in production; or to allow administrators to manage the production environment from their development accounts, some trust relationship must exist between the development and the production account.

  • Finding development roles with a trust relationship with the production account

You can find which development IAM resources are allowed to assume production roles by executing:

$ python -m awsdigger --filter "Action~'sts:AssumeRole'"

or being more specific by using:

$ python -m awsdigger --filter "Action~'sts:AssumeRole' and (Resource~'1111111111111' or Resource='*')"
  • Finding production roles with a trust relationship with the development account

Powerful roles, users or groups having a trust relationship with the development account could be identified by executing the following in production:

$ python -m awsdigger --filter "Trusted='arn:aws:iam::123456789012:root' and Action='*' and Resource='*'"

References:

[1] https://aws.amazon.com/blogs/security/how-to-use-a-single-iam-user-to-easily-access-all-your-accounts-by-using-the-aws-cli/

[2] https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html

[3] https://aws.amazon.com/answers/account-management/aws-multi-account-security-strategy/

[4] https://docs.aws.amazon.com/IAM/latest/UserGuide/list_iam.html

--

--