Bypassing GitHub branch protection with OIDC and GitHub Environments

A part of deploying to AWS with GitHub Actions is the idea of using an OIDC provider to allow for temporary credentials that can be assumed during the pipeline to enable secure access to AWS accounts.

The documentation for which is located here shows that you should configure your trust policy like so:

"Condition": {
  "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:ref:refs/heads/octo-branch"
  }
}

This configuration ensures that the only way that anyone can assume the deployment role is if the subject (sub) matches the specific branch name octo-branch. If a deployment attempts to assume the role outside of that branch it will be denied, combined with standard branch protections (no code pushed directly to branch, must have been via a Pull Request and have approvals by other approved members) ensures that the only way code can be deployed is via the review and approval process.

Contrary to the above, the documentation continues on to provide a way to create a Trust Policy when using GitHub environments:

If you use a workflow with an environment, the sub field must reference the environment name: repo:OWNER/REPOSITORY:environment:NAME.

"Condition": {
  "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:environment:prod"
  }
}

Herein lies the issue: There is no longer any reference to the branch name

Github does provide the branch name as the ref value of the JWT shown here so a way to validate the branch name is possible.

When we go to check which values we can use as part of the Trust Policy condition operators here

The available keys are:

  • amr
  • aud
  • id
  • sub

And the following Note:

No other web identitiy based federation condition keys are available for use after the external identity provider (IdP) authentication and authorization for the initial AssumeRoleWithWebIdentity operation.

So our new trust policy has no access to the ref provided by GitHub which now means we have no way to check which branch the request is coming from.

Testing the process

I’ve created this repo as an example of how this works.

Firstly we have full branch protection for main enabled and we get the approriate error when attempting to push

Push Denied

Our AWS Trust policy is configured with branch protection:

Trust Policy

And our Merge is blocked:

Merge Blocked

The process works wholly as we expect, once the review is complete and the merge happens, the pipeline runs and we get deployed code:

Pipeline Success

Malicious user enters the chat

Let’s say we see this open source repository, and we want to be a bad person, then we can attempt to run our own actions inside a branch, and using GitHub Actions on: push we can essentially run anything we want even in branches.

So we create a new branch, make some small modifications to our pipeline config and attempt to deploy code:

Malicious Changes Nefarious Action Running

We have been thwarted by our OIDC trust policy!

Thwarted

Move to GitHub Environments

Now, as a Developer, we want to try the Environments feature of Github to manage our deployments and and so we can have different secrets and reuse our pipeline configurations. We enable it, realise that our Trust Policy no longer works (see above, the change in sub) so we update our Trust policy as described in the GitHub documentation

Updated Trust Policy

And add the environment to our pipeline, proceed through the review and merge process, and watch our updated pipeline perform as expected.

Added environment Environment Deployed

Malicious user number two

Another bad actor notices this new change to use environments, so he attempts to do a deployment as well.

Remember that now we are no longer able to rely on the Trust Policy to limit our credentials to a specific branch because there is no reference to the branch in the Trust Policy due to the limited subset of options provided to us.

We can update the malicious branch with the new environment and push to our branch:

Updated Branch

Nefarious Action 2

Deloyed malicious bucket

Buckets

Conclusion

By simply following the documentation for configuring an OIDC provider that makes use of GitHub Environments you are completely able to bypass the branch protection feature of GitHub and use credentials that should be locked down to use by only a single branch.

In my opinion no user of the AWS Identity Provider should be using the GitHub Environment feature as it (by default, and with the expected setup based on documentation) leaves your pipelines vulnerable to a simple attack.

Remediation

If you MUST use GitHub environments for your deployments, to ensure that your credentials may not be assumed by any branch, you can customize your GitHub claim token and add that new custom claim to your trust policy. Unfortunately this is not required so there will be projects out there with this vulnerability.