[go: up one dir, main page]

Skip to content

CI ID Token Manipulated claims vulnerability for forked projects MR pipelines

Related issue

⚠️ Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #3137660 by skybound on 2025-05-09, assigned to @greg:

Report | Attachments | How To Reproduce

Report

Summary

An attacker can trick GitLab into issuing, and subsequently using, an ID token for a protected branch of a repository which they do not have push or merge permissions for through the use of forks and merge request pipelines. This could be used to authenticate with other services such as AWS, gaining access to resources where access is restricted through the protected branches of the repository.

Steps to reproduce
  1. Create a victim project with a main protected branch
  2. Configure the repository to only allow maintainers to push/merge into the main protected branch
  3. Commit the first-gitlab-ci.yml (first-gitlab-ci.yml) as .gitlab-ci.yml into the victim project directly to the main branch
  4. In the resultant pipeline, note the sub claim
  5. Add a second user as a developer to the project

The following steps are to be taken as the second user:

  1. Fork the victim repository, the fork will be referenced as the attacker repository
  2. Run the pipeline manually within the attacker repository
  3. In the resultant pipeline, note the sub claim - it should be different to the previous sub claim
  4. Commit the second-gitlab-ci.yml (second-gitlab-ci.yml) to the attacker repository as .gitlab-ci.yml (replacing the previous version) directly to the main branch
  5. Submit a merge request to the victim repository, attempting to merge main from attacker into main
  6. Note a new pipeline is triggered for the merge request
  7. View the sub claim in this pipeline. Note it has the same value as the pipeline from step 4.
Impact

This allows an attacker that has low privileges within a repository to get JWT / ID tokens from branches they may not have push/merge access to. ID tokens are commonly used to authenticate with other services, for example authenticating to AWS via OIDC. The following assumes the third party service is AWS, however similar applies to other services such as Azure. However, this post is limited to AWS for brevity.

Access to AWS is constrained through AWS trust relationships which check the sub claim which by default validates the repository name and branch (as described in https://docs.gitlab.com/ci/cloud_services/aws/#configure-a-role-and-trust). For example, the sub field would be project_path:mygroup/myproject:ref_type:branch:ref:main if the pipeline was run from the main branch in the project at mygroup/myproject.

An attacker through their fork could modify .gitlab-ci.yml to extract the ID token for the main branch of the victim repository. This would allow them to authenticate to third party services as if they were the main branch of the victim repository. If the repository hosted Terraform code that was deployed automatically into an AWS account through CI/CD with trust relationships configured to only allow the main branch of the victim repository to access an administrative deployment role, an attacker would directly gain administrative access to the AWS account.

Examples

victim repository = https://gitlab.com/Skybound/oidc\ pipeline from step 4 = https://gitlab.com/Skybound/oidc/-/jobs/9990314446\ attacker repository = https://gitlab.com/rs_mohit/oidc\ pipeline from step 7 = https://gitlab.com/rs_mohit/oidc/-/jobs/9990324982\ pipeline from step 12 = https://gitlab.com/Skybound/oidc/-/jobs/9990328384

What is the current bug behavior?

The sub field from the merge request pipeline is the same as if the pipeline was directly run from the main branch.

What is the expected correct behavior?

The sub field from the merge request pipeline should not be the same as the value when running the pipeline directly against main.

Relevant logs and/or screenshots

The JWT token claims when running against the main branch of the victim repository:

{
  "namespace_id": "[REDACTED]",  
  "namespace_path": "[REDACTED]",  
  "project_id": "[REDACTED]",  
  "project_path": "[REDACTED]/oidc",  
  "user_id": "[REDACTED]",  
  "user_login": "Skybound",  
  "user_email": [REDACTED],  
  "user_access_level": "owner",  
  "pipeline_id": "1809987654",  
  "pipeline_source": "push",  
  "job_id": "9990314446",  
  "ref": "main",  
  "ref_type": "branch",  
  "ref_path": "refs/heads/main",  
  "ref_protected": "true",  
  "runner_id": 32976687,  
  "runner_environment": "gitlab-hosted",  
  "sha": "[REDACTED]",  
  "project_visibility": "private",  
  "ci_config_ref_uri": "gitlab.com/[REDACTED]/oidc//.gitlab-ci.yml@refs/heads/main",  
  "ci_config_sha": "[REDACTED]",  
  "jti": "[REDACTED]",  
  "iat": 1746820333,  
  "nbf": 1746820328,  
  "exp": 1746823933,  
  "iss": "https://gitlab.com",  
  "sub": "project_path:[REDACTED]/oidc:ref_type:branch:ref:main",  
  "aud": "sts.amazonaws.com"  
}

The JWT token claims when running against the main branch of the attacker repository:

{
  "namespace_id": "[REDACTED]",  
  "namespace_path": "[REDACTED]",  
  "project_id": "[REDACTED]",  
  "project_path": "[REDACTED]/oidc",  
  "user_id": "[REDACTED]",  
  "user_login": "[REDACTED]",  
  "user_email": [REDACTED],  
  "user_access_level": "owner",  
  "pipeline_id": "1809988925",  
  "pipeline_source": "web",  
  "job_id": "9990324982",  
  "ref": "main",  
  "ref_type": "branch",  
  "ref_path": "refs/heads/main",  
  "ref_protected": "true",  
  "runner_id": 12270831,  
  "runner_environment": "gitlab-hosted",  
  "sha": "[REDACTED]",  
  "project_visibility": "private",  
  "ci_config_ref_uri": "gitlab.com/[REDACTED]/oidc//.gitlab-ci.yml@refs/heads/main",  
  "ci_config_sha": "[REDACTED]",  
  "jti": "[REDACTED]",  
  "iat": 1746820450,  
  "nbf": 1746820445,  
  "exp": 1746824050,  
  "iss": "https://gitlab.com",  
  "sub": "project_path:[REDACTED]/oidc:ref_type:branch:ref:main",  
  "aud": "sts.amazonaws.com"  
}

The JWT token claims when running the main branch of the attacker repository as a merge request pipeline within the victim repository:

{
  "namespace_id": "[REDACTED]",  
  "namespace_path": "[REDACTED]",  
  "project_id": "[REDACTED]",  
  "project_path": "[REDACTED]/oidc",  
  "user_id": "[REDACTED]",  
  "user_login": "[REDACTED]",  
  "user_email": [REDACTED],  
  "user_access_level": "developer",  
  "pipeline_id": "1809989379",  
  "pipeline_source": "merge_request_event",  
  "job_id": "9990328384",  
  "ref": "main",  
  "ref_type": "branch",  
  "ref_path": "refs/heads/main",  
  "ref_protected": "false",  
  "runner_id": 12270831,  
  "runner_environment": "gitlab-hosted",  
  "sha": "[REDACTED]",  
  "project_visibility": "private",  
  "ci_config_ref_uri": "gitlab.com/[REDACTED]/oidc//.gitlab-ci.yml@refs/heads/main",  
  "ci_config_sha": "[REDACTED]",  
  "jti": "[REDACTED]",  
  "iat": 1746820490,  
  "nbf": 1746820485,  
  "exp": 1746824090,  
  "iss": "https://gitlab.com",  
  "sub": "project_path:[REDACTED]/oidc:ref_type:branch:ref:main",  
  "aud": "sts.amazonaws.com"  
}

Branch Protection Rules showing maintainer only:
image.png

Output of checks

This bug happens on GitLab.com and self-managed instances

Impact

An attacker can leverage this issue to obtain ID tokens for protected branches of repositories. This could allow them to authenticate and access third party services that trust GitLab as an OIDC provider. For example, administrative access to cloud accounts.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce

Please add reproducibility information to this section:

Implementation plan

  • Create a new derisk feature flag called use_forked_project_claims_in_ci_id_tokens
  • For cross project MR pipelines use source project of MR as the project
  • Code logic changes here and here
  • Update docs
  • Get MR reviewed and merged and deployed
  • Rollout the feature flag
  • Cleanup the feature flag
Edited by ADandy