Skip to Main Content
June 15, 2023

Control Tower Pivoting Using the Default Role

Written by Patrick Mayo
Cloud Assessment Cloud Baseline Configuration Review Cloud Penetration Testing

Introduction

The cloud security landscape for AWS has continued to evolve each year to become a complex set of products and best practices with the goal of maintaining a mature security posture. AWS Organizations was released in 2017[1] and has been a major solution to aid in managing the multi-account AWS environment that the cloud provider advocates that customers leverage to maintain a secure operating environment. AWS Control Tower was later released in 2019[2] and builds upon AWS Organizations to automate provisioning accounts and maintain a baseline security configuration for a multi-account AWS environment.

AWS Organizations and Control Tower are key tools the service provider recommends for cloud best practices.[3] Control Tower management for a multi-account AWS environment through the Control Tower Landing Zone, an interface that can be used to specify policy requirements with Service Control Policies (SCPs), regulates operations to approved regions, centralized logging for each Member account to a dedicated Audit account, and drift detection and remediation.

Centralized management and enforcement for policies, permissions, and configurations sounds like an ideal solution for administrators, and it very much is. However, because access is provided to multiple accounts in the AWS environment, it is also a high-value target. Today, we will review a pivoting technique that may be used if access to the Control Tower Management account is obtained, how detections can be configured, what preventive changes can be made to strengthen defense, and, finally, what remediations can be implemented to limit access for an identity that may be utilizing this attack method.

Attack Path Details

When a Landing Zone is first completed in the Management Account for an AWS Control Tower configuration, the service creates a new IAM role named AWSControlTowerExecution in each Member account. This new IAM role is configured with the AWS-managed AdministratorAccess policy and includes a Trust Relationship policy. This policy allows an identity in the Management account to assume the role and is used by Control Tower to run a series of CloudFormation stacks in each Member account based on the Control Tower configuration.

Figure 1 - AWSControlTowerExecution Role Configuration

With this default configuration, assuming the AWSControlTowerExecution IAM role only requires that two (2) conditions are met:

  1. The assuming identity must have Allow permissions to perform the sts:AssumeRole action against the AWSControlTowerExecution IAM role in the Member account.
  2. The assuming identity must be issuing the sts:AssumeRole request from the Management account.

A potential pivoting attack path exists from the Management account to each Member account, partially due to this default Trust Relationship policy and its lack of additional conditional requirements. Because of this default Trust Relationship policy, if an identity in the Management account is allowed to perform the sts:AssumeRole action and also contains a 'weakly defined' resource definition in the IAM policy, then the AWSControlTowerExecution IAM role in the Member account can be assumed. Examples of a 'weakly defined' resource definition include '*', 'arn:aws:iam::*:role/*', and 'arn:aws:iam::<MEMBER-ACCOUNT-ID>:role/*'.

Testing

Testing if the AWSControlTowerExecution IAM role in each Member account can be assumed is made simple due to AWS Control Tower using the same name with each Member account that is added to the Control Tower Landing Zone. Acquiring access in the Management account is outside the scope of this blog post, but a series of checks can be completed to test if assuming the AWSControlTowerExecution IAM role in a Member account is possible:

  1. Check if the account you are operating in is the Management account. These resources can be help determine if you may be operating in a Management account:
    • Check the Control Tower configuration directly.
    • Check for the IAM AWSControlTowerAdmin role.
    • Look for Control Tower stacks or StackSets in CloudFormation.
    • See if a 'controltower' Trail is in CloudTrail.
    • See if an 'aws-controltower/CloudTrailLogs' Log Group is in CloudWatch.
  2. (Optional): If you find yourself in a Management account and you are unable to view your IAM policies directly, you can test if the identity you are acting under may have a 'weakly defined' resource definition by assuming an IAM role in an account under your control. However, it should be noted that both successful and failed attempts at this are logged in the account making the assume request, so you may want to skip this step during Red Team engagements.
    • See if an 'aws-controltower/CloudTrailLogs' Log Group is in CloudWatch
  3. Knowledge of the Member account ID is required in order to test assuming the AWSControlTowerExecution IAM role. These Member account IDs can be searched for within the Management account. A small sample of cli queries that may lead to discovering Member account IDs has been recorded in my Git repository.[4]
  4. Finally, with the Member account ID, an Assume Role request can be attempted using the identity you are acting as in the Management account by updating the placeholder in this ARN string: arn:aws:iam::<MEMBER-ACCOUNT-ID>:role/service-role/AWSControlTowerAdmin. This can be tested by using the 'Switch Role' option in the Console,[5] specifying the role ARN in the AWS config file with a source_profile,[6] or assuming the role from the command line[7] and passing the results to my r2p script[8] to easily add it to the credentials file.

If you do not have access to an identity that permits the sts:AssumeRole action with a 'weakly defined' resource statement, then attempting to elevate the privileges may help. For a user or role configured in traditional IAM, privilege escalation methods identified in Rhino Security Lab’s privilege escalation article[9] can be considered.

However, if the identity is managed through AWS Identity Center, then attempting to add or update the Permission Set's policy may provide the required permissions. The most reliable method of elevating permissions granted by a Permission Set is by adding an in-line policy, as in-line policies are applied immediately.

Detection

Detection tooling can be configured to monitor and alert for errant sts:AssumeRole actions taken against the AWSControlTowerExecution IAM role in each Member account. This can be further isolated to include Assume Role actions not sourced from AWS services. An example event of an identity assuming the default AWSControlTowerExecution role in a Member account can be found in the collection of test events shared.[10]

Defense

When planning how to prevent an illegitimate assuming of the AWSControlTowerExecution IAM role, the first notion may be to update the Trust Relationship policy on this role to include additional condition requirements. Doing so would accomplish the goal, but it will also be flagged as drift by Control Tower, and if the drift is ever remediated, then the changes to the Trust Relationship policy will be lost.

A better approach is to add a Permissions Boundary that prevents sts:AssumeRole actions by users or roles in the Management account, which can be applied to traditional IAM users and roles as well as Permission Set in Identity Manager. This will prevent the user or role with the Permissions Boundary from assuming the AWSControlTowerExecution IAM role in each Member account.

The strongest defense against this manner of pivoting attack is to maintain well-defined access policies in the Management account, regardless of whether they are granted to an identity through traditional IAM user and role management or SSO integrations such as AWS Identity Center. While reviewing policies, attention should be given to which actions are allowed but also to how broadly defined resource statements are configured.

While reviewing permissions granted to a traditional IAM user, group, or role is straightforward, the same cannot be said about Identity Center and its Permissions Sets. Mapping which identities are configured with which Permission Sets and attached to which accounts in the AWS Identity Center console is an experience that leaves a bit to be desired. To hopefully alleviate this concern, we developed a script[11] that will output a simple CSV-formatted list of identities and the Permissions Sets that are provisioned.

Remediation

Responding to an incident can be a hectic time, and an administrator may assume that modifying or removing permissions from an identity in IAM or Identity Center would immediately apply that change. However, that is not how AWS session duration functions currently. As explained by AWS[12], provisioned access is valid until the initially configured expiration time is reached. Removing permissions from users, and even deleting a user entirely, does nothing to prevent the originally provisioned access.

Thankfully, Control Tower configured the AWSControlTowerExecution role in each Member account with the lowest possible session duration of one (1) hour. So, if this role is compromised, the maximum access time will be a single hour before a new sts:AssumeRole request is needed. However, a lot of actions, such as setting up persistence, exfiltrating data, or encrypting data with a foreign Key Management Service (KMS) key for ransomware can still occur in that account within that one (1) hour timeframe. AWS has previously released documentation[12] on how to revoke federated users’ access with SCPs or add a Deny statement to the role that the identity has assumed.

During testing, we found that neither of the proposed solutions fully works as intended in a Control Tower configuration. The proposed Deny policy is unable to be applied to the IAM roles managed by Identity Center due to their being protected by AWS in some manner. Attempting to do so produces the error message Policy deny-access-revoked-users not added. Cannot perform the operation on the protected role '<ROLE_NAME>' - this role is only modifiable by AWS. SCPs appear to be a valid solution for Member accounts, but this does not help us with this attack path because SCPs are unable to be applied to the Management account.

Through additional testing and working with AWS support, the best option is to add a Deny policy to the identity in the form of an in-line policy. Where the in-line policy is applied depends on whether the identity permitted the sts:AssumeRole action through a traditional IAM user, group, or role or it was provided through Identity Center’s Permissions Sets. Because Identity Center’s Permissions Sets can be troublesome to map out, this script[13] was created to hopefully help provide better visibility on where sts:AssumeRole is being sourced from for IAM Identity Center resources.

Conclusion

AWS Control Tower provides a convenient location to centrally manage policies against multiple accounts in your AWS environment. But inversely, this level of centralized convenience attracts a high level of interest from attackers and should therefore be carefully reviewed and monitored.

As an attacker, a few simple checks can be made to see if luck was on your side and you find yourself in the Control Tower Management account. In such a case, searching for Member account IDs and testing to see if the AWSControlTowerExecution role can be assumed may provide additional administrative access to Member accounts.

Conversely, when engineering a Control Tower configuration, adding a few additional Permissions Boundaries to the users and roles in IAM, as well as the Permissions Sets in Identity Center, may help better protect this sensitive role in each Member account. Additional reporting can also be configured around assume-role attempts not sourced from AWS services, so that defenders can properly investigate the cause.

And finally, in the case of a compromised identity, applying an in-line policy to deny access to specific identities has been seen to provide immediate revocation of accesses. Conversely, removing permissions from an identity, and even deleting the identity, does nothing to revoke already provisioned access due to how AWS session duration is configured.

References

  1. https://aws.amazon.com/about-aws/whats-new/2017/02/aws-organizations-now-generally-available/
  2. https://aws.amazon.com/blogs/aws/aws-control-tower-set-up-govern-a-multi-account-aws-environment/
  3. https://aws.amazon.com/organizations/getting-started/best-practices/
  4. https://github.com/zombyte/ts-blog-posts/blob/main/pivoting_with_assumerole_from_management_account/searching_for_member_account_ids.md
  5. https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-console.html
  6. https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html
  7. https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role.html
  8. https://github.com/zombyte/ts-blog-posts/blob/main/pivoting_with_assumerole_from_management_account/r2p
  9. https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/
  10. https://github.com/zombyte/ts-blog-posts/blob/main/pivoting_with_assumerole_from_management_account/cloudtrail_events_for_detections.md
  11. https://github.com/zombyte/ts-blog-posts/blob/main/pivoting_with_assumerole_from_management_account/org_permissions_attachments.py
  12. https://aws.amazon.com/blogs/security/how-to-revoke-federated-users-active-aws-sessions/
  13. https://github.com/zombyte/ts-blog-posts/blob/main/pivoting_with_assumerole_from_management_account/org_assumeRole_search.py