Update: a few weeks after this publication, GitHub decided to fix the issue and employed the mitigation we recommended to them in our initial report. The mitigations section of this post was updated accordingly; read it to make sure your repositories are well-configured and safe from this kind of attack.
Code reviews are an essential security guardrail, but GitHub’s required code reviewers' settings might be giving you a false sense of security – they can easily be bypassed by any collaborator with reviewer permissions.
See how an attacker can use a compromised account to submit malicious code and merge it into your repository’s main branch while bypassing code review restrictions in this post.
GitHub’s required reviewers capability can be bypassed if currently using this setting to require at least one code review before merging code.
Any code reviewer reviewing code can simply submit malicious code on pull requests during the review process and merge that code to the main branch without review.
GitHub does not currently provide users the ability to eliminate this risk directly.
Even if you followed every agile development best practice perfectly, there are still risks in agile software development. Agile has proven that it offers more benefits than drawbacks, so it’s important to understand the potential security risks so that you can mitigate them ahead of time.
What are Required Reviewers?
A typical development workflow looks like the following:
The developer diverges from the repository’s main development branch
Then the developer adds/deletes/modifies the code as needed.
Finally, a second person must review and approve the changes before that code can be merged back to the main branch.
To support this common workflow, GitHub introduced the concept of Protected Branches. A protected branch is a branch that contains restrictions on who can modify it and how.
For example, the below configuration enforces any developer who wants to push code to the main branch to create a pull request and also get approval from a second, authorized person to approve pull requests.
(A complete list of Branch Protection rules can be found in GitHub’s documentation.)
For obvious reasons, GitHub doesn’t allow developers to authorize their own pull requests – otherwise, there’d be no point in requiring a “required approval.”
However, I’ve discovered that there is an easy method a developer can use to authorize their own pull requests: pull request hijacking.
What is Pull Request Hijacking?
Any user authorized to approve pull requests can modify existing pull requests of other users as desired. After modifying the existing pull requests, they can approve and merge the code without requiring approval. This works because the original pull request was opened by another user (i.e., “User A”), and GitHub doesn't recognize that a different user (i.e., “User B”) modified it.
You can test this yourself:
Find an open pull request.
Modify it or even replace the entire branch content
Approve your own modifications
Bypass required approvals for your modified code
Responsible Disclosure: GitHub Response
When disclosing pull request hijacking to GitHub, they responded that GitHub is working as intended in this scenario.
“This is an intentional design decision and is working as expected. We may make this functionality more strict in the future, but don't have anything to announce right now. As a result, this is not eligible for reward under the Bug Bounty program.” -GitHub
We believe that GitHub didn’t fix this issue because the resulting experience might be too restrictive for modern development teams. There are many frequent and harmless cases in which developers collaborate on the same branch. For example, if I fixed a small typo when reviewing another developer's code, it makes some sense that I should also be allowed to go ahead and push that code.
How to Protect Yourself From Pull Request Hijacking
The following defensive techniques will protect your business from malicious code submissions during a pull request hijack attempt. In addition to these two techniques, you can also lower the risk of malicious code in various ways, like strengthening authentication requirements for developers that are also reviewers and practicing the least privileges in relation to your GitHub organization.
New: Require Approval For The Most Recent Pusher
This mitigation measure was implemented after the initial publication of the blog. When activated, it ensures that all changes made to the code are reviewed by another person before being merged. This ensures that any updates to the code are approved, even if the original pull request was modified by someone else.
Require At Least Two Reviewers
Configure GitHub to require more than one reviewer. This way, even if an existing pull request is modified and approved by the same user, it would still need a review from an additional person. You can configure this with your GitHub settings, as pictured below.
Restrict Who Can Push To Matching Branches
GitHub allows you to create a “personal branch” pattern for each developer that no other developer could modify (pictured below).
Legit Security Can Help
A pre-requisite to hardening your GitHub required reviewer configurations is to have visibility into your development pipelines, know where you are using GitHub, whether or not you are requiring code reviews, and whether you have stale or inactive accounts with reviewer permissions. Legit Security can provide visibility and answers to these questions in minutes. In addition, Legit helps you holistically assess security and compliance risks in your development pipelines across other SCMs and also other developer systems like build servers and artifact registries. Learn more about Legit by visiting our platform page or booking a demo with us today.