Today we’re releasing pathfinding.cloud, an extensive knowledge base that documents the IAM permissions and permission sets that allow for privilege escalation in AWS. Each path in the library specifically calls out the prerequisites required for it to be exploitable, includes attack visualizations, recommended remediation steps, and additional supporting details.
Privilege escalation is a significant concern in AWS environments. Even when an identity does not have administrative access directly, the permissions it does have may allow it to gain that access. We refer to this as having the ability to escalate your privileges, or privilege escalation. Attackers commonly exploit these escalation paths once they have gained initial access to an AWS account, allowing them to expand the permissions available to them.
To reduce this risk, most organizations follow the principle of least privilege when assigning permissions to workloads and human users. Instead of granting administrative-level privileges broadly across their AWS accounts, they limit those permissions to a small set of identities, since compromising any one of them would give an attacker control over the environment.
The library includes more than 60 unique paths, and there is still more work to do. We still need to add other previously documented paths, and new paths are discovered and shared by the community each year. Here’s a preview of the paths list.
Why should we care about IAM privilege escalation?
In 2018, Spencer Gietzen outlined 21 IAM permissions and permission sets that allow for this type of privilege escalation in AWS. Each of these represents a way that one AWS principal (IAM user or IAM role) can either escalate their own permissions directly or gain access to another AWS principal. I’ll refer to these permissions or combinations of permissions as PrivEsc paths, or paths, throughout this post. In the worst-case scenario, these paths would allow a non-administrative principal to gain access to a principal with administrative-level access. That’s a big deal because this is almost always unexpected.
Sometimes, these paths allow a non-administrative principal to gain access to another non-administrative principal. You might think this is no big deal. But if the second principal has access to sensitive data that the first principal does not, this path is still quite important and impactful.
For many practitioners, seeing is believing. In 2019, Bishop Fox’s Gerben Kleijn wrote an article showing how to manually exploit each of the 21 AWS privilege escalation paths from Spencer’s original research. In 2021, I created IAM Vulnerable so penetration testers and security practitioners could deploy those initial 21 paths in their own sandbox environments and then practice exploiting them. During that research, I added 10 additional paths to IAM Vulnerable, bringing the number of supported paths to 31. Most of these new paths were added after looking at the source code of pmapper, an open source tool written by Erik Steringer that tries to detect these privilege escalation paths in an AWS account.
Speaking of open source tools, in follow-up research, also back in 2021, I assessed four open-source IAM PrivEsc assessment tools to see how many paths each of them identified. They ranged from detecting between 14-22 of the 31 paths supported by IAM Vulnerable at the time. But here’s the thing: there are way more than 31 paths!
Gaps in PrivEsc documentation and detection coverage
Over the years, I’ve noticed that new AWS privilege escalation paths are still being introduced and documented. Yet the open source assessment tools in this space are not keeping pace with the new additions, either because the projects are no longer maintained or the authors of the open source tools are not always aware of the latest research. Another thing I’ve noticed is that some paths are known in theory to some because they are so similar to ones that are already documented, but they themselves are not documented. An example of this would be the existing and documented path: iam:PassRole + cloudformation:CreateStack. However, the following variation that uses StackSet’s instead of stacks has not been previously documented but is quite similar: iam:PassRole + cloudformation:CreateStackSet + cloudformation:CreateStackInstances.
The result is that our open source assessment tools are missing many of the paths that allow for privilege escalation. Of the 65 paths currently in the pathfinding.cloud library, 27 of them, or 42%, are not detected by any of the open source tools we evaluated.
What this means in practice is that even organizations that have operationalized linting IAM policies for privilege escalation, or ones that perform automated or semi-automated privilege escalation identification activities, are potentially still missing paths in their environments. Paths that an attacker can exploit!
So how can we fix this disparity between tool coverage, general awareness, and reality?
A structured approach to documenting PrivEsc paths
The approach I’ve taken is to start documenting all of the known privilege escalation paths using a standardized schema, focusing on disambiguation using unique identifiers and unique permissions combinations. Each path also includes explicit explanation steps so that tool authors can reproduce the exploit before adding it into their tools if they choose. The data is completely open-source, so anyone can contribute additional paths by adding a single YAML file.
At this point, if you have not done so already, I encourage you to head over to https://pathfinding.cloud to check it out for yourself. Here’s an example of what the apprunner-001 path looks like:
And for those of you that just enjoy the behind the scenes a bit, I’m going to share some of the decisions I made and the reasons I made them.
Anatomy of a privilege escalation technique
Attack categorization
One of the first decisions I made was to categorize privilege escalation paths into five distinct types.
Self-Escalation involves a principal modifying their own permissions directly (like iam:CreatePolicyVersion on an attached policy).
Principal Access is when you gain access to a different principal without relying on an intermediary compute component, like an EC2 instance or a Lambda. A common example is iam:CreateAccessKey on another user.
New PassRole paths are all of the ones that include iam:passrole. They deserve their own category because they follow a consistent pattern: passing a privileged role to a newly created AWS resource that executes code or commands. Whether it's EC2, Lambda, Glue, or Bedrock, the exploitation is structurally the same.
Existing PassRole paths involve modifying existing resources (such as lambda:UpdateFunctionCode) to gain access to a new principal. These needed separation because while these do not require iam:passrole to exploit, they require a resource of a specific type to exist and to be associated with a role with the right permissions to exploit successfully.
Credential Access involves using a read-level permission that allows you to access a resource that might or might not have credentials in it. I have not added any of these yet, as I’m still unsure if this belongs here. An example of this type would be lambda:ListFunctions. If you have this permission, you will often find additional credentials in lambda environment variables. If you have opinions on whether these belong next to the ones above or deserve their own section, let me know your thoughts!
Permissions: Required versus additional permissions
This and the next section are maybe the two most important to me. Regarding permissions, I separated permissions into two categories: required (minimum needed to exploit) and additional (helpful for reconnaissance). For example, iam:PassRole is required to pass a role to EC2, but iam:ListRoles just helps you discover available roles. The thing is, an attacker might get the helpful permissions from another compromised principal, which is why we don’t consider the read permissions required.
Prerequisites
I’ve taught many penetration testers and security practitioners about AWS privilege escalation over the years and this is one of the hardest concepts to grasp for most. In some cases, even though you have the required permissions, you might be missing some of the other prerequisites needed to launch the attack.
Let’s take the lambda:UpdateFunctionCode permission and break it down. Let’s say you have this permission on all lambda functions.
- If there are no lambda functions, this attack is not possible.
- If there are lambda functions, but none of them are running with an IAM role, you can execute the attack but will not get any new IAM permissions.
- Even if the lambda functions do have IAM roles attached, if none of the lambda functions are running with administrative permissions, then there is no way to directly use this PrivEsc method to gain administrative access.
- Lastly, if you only have
lambda:UpdateFunctionCodeand no other permissions that would allow you to invoke the function, you can update the code, but it will never execute. For this PrivEsc to work, you need something to invoke the function. Sometimes there is already something invoking the function regularly, but other times that is not the case.
So for each and every path in pathfinding.cloud, we include the prerequisites required to gain administrative access and also the prerequisites required to just gain additional IAM permissions.
Path Relationships (Variants)
Speaking of prerequisites and paths, let’s continue thinking about the lambda:UpdateFunctionCode permission. As we stated above, if you only have this permission, this path does not lead to privilege escalation if there is nothing invoking the function. But if you have both lambda:UpdateFunctionCode and lambda:InvokeFunction, that changes things. Having this additional permission removes the prerequisite that something else must invoke the function for the path to work. With these permissions, you can be the one that invokes the function!
I’ve decided to add these variants as unique privilege escalation paths with their own IDs, but I’ve categorized them as variants of primary paths so that they can be differentiated. The reason for this is that some tools already detect some of the variants, while others do not. By giving them each unique identifiers and showing the different exploitation steps for each, I hope to make it easier for tool developers to detect all of these variants.
Learning environments and detection coverage
Two fields in the schema directly address the "awareness gap" problem: learningEnvironments and detectionTools.
For each path, I document which open source and commercial lab environments let you practice it. This serves two purposes: penetration testers can sharpen their exploitation skills, and security teams can validate whether their detection tools actually catch it.
The detectionTools field shows exactly which open source tools detect each path, linking directly to the source code line numbers when possible. When a tool isn't listed, that's a visible gap. This transparency lets security teams understand what they're missing and helps tool maintainers see where to add coverage.
Unique identifiers and machine-readable schema
Every path gets a unique ID like iam-001 or ec2-003. For PassRole paths, I use the service being exploited (not IAM), so iam:PassRole + lambda:CreateFunction becomes lambda-001. This makes it immediately clear which service is involved.
The entire schema defines a machine-readable YAML file that is used for each path, and they all get exported to a single paths.json that powers the site. Here’s a look at one of the most commonly exploited PrivEsc paths, ec2-001: iam:PassRole + ec2:RunInstances in YAML format:
id: ec2-001
name: iam:PassRole + ec2:RunInstances
category: new-passrole
services:
- iam
- ec2
description: A principal with `iam:PassRole` and `ec2:RunInstances` permissions can launch a new EC2 instance and attach an existing IAM Role to it. By accessing this new instance (e.g., via User Data or SSM), the attacker can obtain the credentials of the passed role. The level of access gained depends on the permissions of the available roles.
permissions:
required:
- permission: iam:PassRole
resourceConstraints: Target role ARN must be in the Resource section
- permission: ec2:RunInstances
resourceConstraints: Must have permission to launch EC2 instances
additional:
- permission: iam:ListRoles
resourceConstraints: Helpful for discovering available roles to pass
- permission: iam:GetRole
resourceConstraints: Useful for viewing role trust policies and attached permissions
prerequisites:
admin:
- A role must exist that trusts ec2.amazonaws.com to assume it
- The role must have an instance profile associated with it
- The role must have administrative permissions (e.g., AdministratorAccess)
lateral:
- A role must exist that trusts ec2.amazonaws.com to assume it
- The role must have an instance profile associated with it
discoveryAttribution:
firstDocumented:
author: Spencer Gietzen
organization: Rhino Security Labs
date: 2018
link: https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/
references: [ommitted for brevity]
exploitationSteps: [ommitted for brevity]
recommendation: [ommitted for brevity]
attackVisualization: [ommitted for brevity]
detectionTools: [ommitted for brevity]
learningEnvironments: [ommitted for brevity]
This structure isn't just for the website; it's designed for security tools to consume. Want to check if your CSPM covers all Lambda-based PassRole escalations? Query for category: service-passrole and services: [lambda]:
curl -sk https://pathfinding.cloud/paths.json | jq '.[] | select(.category == "service-passrole" and (.services | contains(["lambda"]))) | {id, name}'
{
"id": "lambda-001",
"name": "iam:PassRole + lambda:CreateFunction + lambda:InvokeFunction"
}
{
"id": "lambda-002",
"name": "iam:PassRole + lambda:CreateFunction + lambda:CreateEventSourceMapping"
}
{
"id": "lambda-006",
"name": "iam:PassRole + lambda:CreateFunction + lambda:AddPermission"
}
Want to see which paths PMapper misses? Filter for paths where detectionTools.pmapper is absent.
curl https://pathfinding.cloud/paths.json | jq '.[] | select(.detectionTools.pmapper == null) | {id, name}'
{
"id": "apprunner-001",
"name": "iam:PassRole + apprunner:CreateService"
}
{
"id": "apprunner-002",
"name": "apprunner:UpdateService"
}
{
"id": "bedrock-001",
"name": "iam:PassRole + bedrock-agentcore:CreateCodeInterpreter + bedrock-agentcore:StartCodeInterpreterSession + bedrock-agentcore:InvokeCodeInterpreter"
}
[and many more...]
Acknowledgements
Thanks to Daniel Grzelak for our brainstorming session about taxonomy. During this research Daniel and I realized we were both working on similar projects and we ended up providing bi-directional feedback. Daniel is also set to release something in this space very soon, so keep an eye out for it.
Conclusion
The gap between documented privilege escalation paths and tool coverage isn't going away. AWS keeps adding services, and each new service brings new privilege escalation variations. But the gap doesn't need to be invisible.
pathfinding.cloud makes these gaps visible and actionable. Security teams can see exactly which paths their tools miss, penetration testers can find less-documented techniques, and tool builders can identify where to add coverage.
If you secure AWS environments, I encourage you to browse the documented paths and check your tool coverage. If you're a researcher who's identified a new path or variation, or if you see mistakes in attribution or any other fields, please submit a pull request.
The goal isn't just comprehensive documentation. It's making sure that when someone deploys AWS security tools and fixes all the findings, they actually closed the privilege escalation paths they think they did.
Visit pathfinding.cloud to explore all documented paths, or check out github.com/DataDog/pathfinding.cloud to contribute.