IAMGraph: Mapping Cross-Account Attack Paths in AWS Environments

By Aleksi Kallio

 

Cross-account IAM role trust relationships can create complex attack paths that are hard to detect in AWS environments. In large AWS organizations mapping such paths manually is very laborious due to many different configurations involved.

This post describes IAMGraph, a tool that helps analyse cross-account IAM role access by modelling the relevant IAM configurations in a Neo4j graph database and analysing relevant IAM policies with IAMSpy to find effective assume-role paths. The generated graph can then be queried to identify dangerous attack paths. IAMGraph has been developed to identify potential cross-account attack paths from large AWS environments consisting of up to hundreds of accounts.

Background

In AWS, an account serves as a fundamental security boundary. Resources and data in one AWS account are logically isolated from resources and data from others. According to best practices, workloads of different services and environments should be separated into different accounts. One reason behind this recommendation is to limit the blast radius of a potential security incident to the account where the incident has happened.

However, IAM roles can be used to breach this boundary through cross-account role assumption. IAM roles are attached with a trust policy that can define AWS principals trusted by the role. Principals defined in the trust policy are trusted to assume the role and get access to associated temporary credentials for the role. This trust policy can define principals in any AWS account, not just the account the role is in.

In addition to the roles' trust policies, the effective assume-role paths depend on the identity-based policies attached to the trusted principals. When the trusted principal is in another account, it needs to have permissions in its identity-based policies allowing assuming the destination role. Moreover, the trust policy can define whole account as trusted. In this case, any principal in the trusted account with enough permissions in its identity-based policies can assume the role.

While such cross-account roles have many legitimate use cases, they introduce further complexity which could expose a greater attack surface. An attacker who has access to an IAM principal in one account, could move laterally to other AWS accounts by assuming roles that trust the breached principal. Moreover, assuming roles could allow an attacker to escalate their privileges if they're able to assume a role with higher privileges than they currently have. This could significantly increase the impact of a security incident in an AWS account.

In a large AWS environment identifying such attack paths and removing unnecessary cross-account configurations can be challenging. In an AWS organization consisting of hundreds of accounts and thousands of IAM roles, analysing all configurations involved manually to identify dangerous paths and access patterns is very laborious. 

Approach

To assist in identifying cross-account attack paths in AWS environments, WithSecure has developed IAMGraph. IAMGraph is a tool that parses the IAM configuration data of in-scope AWS accounts and models the trust relationships between the IAM principals as a graph in a Neo4j graph database. Moreover, it uses IAMSpy to analyse the permissions of IAM principals in the graph to identify cross-account access.

How it Works

The tool works with the output of AWS CLI command aws iam get-account-authorization-details. The command needs to be executed and its output stored for each target AWS account in scope. Use of a single command per account reduces time for data collection which in turn allows for analysing large environments in a shorter timeframe.

The tool parses the gathered IAM data and models the Accounts, IAM users and IAM roles to the graph in Neo4j. At this phase, a relationship of type IN from each IAM principal node in the dataset is created to an account node that represents the account the principal is in.

graph-1

After this, the tool generates edges of type TRUSTS from nodes representing IAM roles to the nodes trusted by each role. These edges are generated by parsing the trust policies of each role. If the role defines an account, IAM user or IAM role in its trust policy, edges to the nodes representing these principals are created. It is possible that a principal trusted by a role is not part of the original dataset and thus not in the database. If this is the case, a node representing the trusted principal is created.

After this phase, the graph looks like the following:

graph-2

Finally, the tool uses IAMSpy to validate assume role paths between the nodes. As described in AWS documentation, defining a cross-account principal in a trust policy (or any other resource-based policy) is only half of establishing the trust relationship. The trusted principal also needs to have an identity-based policy that grants it permissions to assume the destination role. Therefore, IAMSpy is used to find which of the trusted principals meet this criteria. This makes the graph more useful as the user can focus on the effective attack paths.

If a role defines an account in its trust policy, IAMSpy is used to find all principals in the account that can assume the role. From each such principal, a relationship of type CAN_ASSUME  is created to the node representing the role. If the role defines an IAM user or role in its trust policy, IAMSpy is used to check if this principal has adequate permissions to assume the destination role. If it does, a CAN_ASSUME  relationship is again generated to the role from the trusted node. After this stage, the final graph could look like the following:

graph-3

The database can now be queried to identify potential attack paths from the generated graph. Below is an example of how the tool could be used to analyse a small AWS environment to identify trust relationships an attacker could exploit to move laterally between the accounts.

Example

The demo environment consisted of three AWS accounts. The accounts had an IAM user and three IAM roles with the trust policies shown below:

demo-env-iam-config

Each user and role also had identity-based policies with permissions to assume any role.

Before using the tool, a Neo4j database instance needs to be running and IAM data needs to be collected from all of the target accounts. The needed IAM data can be collected by running the aws iam get-account-authorization-details command on each account and storing its output to a directory:

$ aws iam get-account-authorization-details --profile account-1 > ~/test-data/account-1.json
$ aws iam get-account-authorization-details --profile account-2 > ~/test-data/account-2.json
$ aws iam get-account-authorization-details --profile account-3 > ~/test-data/account-3.json
$ ls ~/test-data
account-1.json  account-2.json  account-3.json

After this, IAMGraph can be run with the following:

$ iamgraph --db-uri bolt://localhost:7687 run --input-dir ~/test-data/

Executing the tool like this will have it perform all the phases described earlier. After the tool is finished, the Neo4j browser interface can be used to query the database and inspect the graph model generated. For small datasets such as this example, the whole graph model can be queried with a Cypher query like:

MATCH (n)-[r]-() RETURN *
all-data-query-results

The graph can be made more readable by modifying the node and relationship styles in the Neo4j browser UI.

With larger datasets the resulting graph would be too large to be queried at once, and more refined queries would need to be used. One approach in larger environments is to first identify valuable accounts, such as accounts with sensitive production data, or key accounts within the organization, such as the management account, and use those a focal point for analysis.

As an example, if the account 111111222222 in the demo dataset was an account storing sensitive production data, we could be interested in identifying attack paths that target that account. We could achieve this with a Cypher query like the following:

MATCH (src_a:Account)<-[i:IN]-(src_p)-[ca:CAN_ASSUME*]->(dst_r:IAMRole)-[ii:IN]->(dst_a:Account) WHERE dst_a.AccountId='111111222222' RETURN *

The above queries source principals src_p that can assume roles dst_r in destination account dst_a. The WHERE clause in the end limits the destination account to the account of interest. The asterisk in CAN_ASSUME relationship in the query matches 1 to any number of hops, so there can be paths with multiple CAN_ASSUME relationships chained in the returned graph.

With the demo dataset, the above query would return the following graph:

query-result

As can be seen from the graph above, the admin-role IAM role in the production account on the right-hand side, could be assumed by CLIUser in another account by chaining three role assumptions through two accounts. In other words, if an attacker compromised the access keys of the CLIUser in account 888888999999, they could access the admin-role in the production 111111222222 account by leveraging the trust relationships configured between the IAM principals in the environment.

Additional Use Cases

The example above shows how the tool could be used with a small and simple target environment. However, IAMGraph was originally built to assist in assessing IAM configurations of large AWS environments consisting of hundreds of accounts.

Below is an example of how a similar query that was used in the example above would look like in a larger, and more realistic, environment:

large-env-graph

As seen, with an environment of this size, identifying dangerous roles and potential attack paths might require additional analysis and further queries. However this query could still work as a good starting point.

In addition to identifying assume role paths between the accounts in the environment, an owner of an AWS organization could be interested in finding IAM roles that trust principals not within their organization. Such roles could be considered risky, as they could be used to gain access to the organization from external sources.

If the input dataset used to build the graph contains IAM configurations from all accounts within the organization, detecting IAM roles that trust external accounts can be found with the following Cypher query:

MATCH (a)<-[t:TRUSTS]-(r) WHERE a.InDataset=false RETURN *
external-trust-example

InDataset is set automatically by IAMGraph when it processes the data. If the principal is within the source IAM data it is set to true, else it is set to false when parsed from a trust policy. This property can then be used in queries to identify nodes not part of the input dataset.

Role's trust policy could also be configured to trust a wildcard (*) principal. This means that anyone could assume this role. This is obviously a security risk. Such roles can be found from the graph with a query like the following:

MATCH (p:IAMPrincipal {ARN:'*'})<-[t:TRUSTS]-(n) RETURN *

These are some examples of information and attack paths that can be queried from the graph generated by IAMGraph. Depending on the input dataset and additional information available of the target environment, the generated graph could be enriched to allow various different analysis.

Conclusions

IAMGraph can be used to automatically model the cross-account IAM trust relationships between multiple AWS accounts to identify potentially dangerous access paths. Although IAMGraph can help in finding cross-account attack paths, IAM is highly contextual and actually identifying dangerous configurations still requires some manual analysis. IAMGraph, with the help of IAMSpy, simplifies such assessments but the user is left with judging the risk associated with different access paths.

Related content

IceKube: Finding complex attack paths in Kubernetes clusters

This blog post explores the security implications of enabling Large Language Models (LLMs) to operate web browsers, highlighting the dangers of prompt injection vulnerabilities. It presents two case studies using Taxy AI, a prototype browser agent, to show how attackers can exploit these vulnerabilities to extract sensitive data from a user's email and manipulate the agent into merging a harmful pull request on GitHub. 

Read more

Dangers of a Service as a Principal in AWS Resource-Based Policies

Amazon Web Services (AWS) allow permissions policies to be attached to particular sets of resources allowing for granular control. We look into how they can be used effectively, and what happens if they are misconfigured. In particular, the implications of allowing an AWS service to act as a Principal are discussed and how this could expose an environment to abuse.

Read more