Bypassing Code Owners branch protection rule in GitLab
:warning: **Please read [the process](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/developer.md) on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.** **[HackerOne report #1898054](https://hackerone.com/reports/1898054)** by `inspector-ambitious` on 2023-03-09, assigned to @cmaxim: [Report](#report) | [Attachments](#attachments) | [How To Reproduce](#how-to-reproduce) ## Report #### Summary I believe I discovered a vulnerability that could potentially allow an attacker with Developer Role access to a repository to circumvent Code Owners approval branch protection when submitting merge requests. This could be accomplished by creating a tag with the same name as the protected branch, which would render the Code Owners approval rule ineffective. The following is extracted from this [documentation](https://git-scm.com/docs/gitrevisions#Documentation/gitrevisions.txt-emltrefnamegtemegemmasterememheadsmasterememrefsheadsmasterem). > A symbolic ref name. E.g. `master` typically means the commit object referenced by `refs/heads/master`. If you happen to have both `heads/master` and `tags/master`, you can explicitly say heads/master to tell Git which one you mean. When ambiguous, a `<refname>` is disambiguated by taking the first match in the following rules: > > If $GIT_DIR/`<refname>` exists, that is what you mean (this is usually useful only for HEAD, FETCH_HEAD, ORIG_HEAD, MERGE_HEAD and CHERRY_PICK_HEAD); > > - otherwise, refs/`<refname>` if it exists; > > - otherwise, refs/tags/`<refname>` if it exists; > > - otherwise, refs/heads/`<refname>` if it exists; > > - otherwise, refs/remotes/`<refname>` if it exists; > > - otherwise, refs/remotes/`<refname>`/HEAD if it exists. Consequently by abusing git ambiguous ref name, an attacker could merge the request without the required approval, because a `CODEOWNERS` file inside a tag reference will take precedence over the one in the branch assuming both tag and branch have the same name. #### Steps to reproduce To reproduce this vulnerability we will use one repository and 2 accounts - a maintainer/victim: root - a developer/attacker: inspector ##### Step 1: Repository setup (victim context) 1. Create a blank private repository with a `README`. 2. Add a `CODEOWNERS` file with the following content ``` * [@]root ``` ![image.png](https://h1.sec.gitlab.net/a/1f4d4419-2793-48a9-983b-1969591f4ca9/image.png) 3. Create another branch called `devel` based on `main` ![image.png](https://h1.sec.gitlab.net/a/23efd73e-6cac-4233-b2ec-75982c78a038/image.png) Please note that since we can't create tags that matches the default branch, this attack can only target a non default branch (hence we will run it against `devel`) 4. Go to `Settings > Repository` and add `devel` as a protected branch. ![image.png](https://h1.sec.gitlab.net/a/3761cb2e-8103-4633-b75d-ed383fdf3afa/image.png) Allowed to merge: Developers + Maintainers Allowed to push and merge: Maintainers Allowed to force push: Disabled Code owner approval: Enabled 5. Go to `Setting > Merge Requests` and at the bottom of the page tick the box `Prevent editing approval rules in merge requests` and save the changes. ![image.png](https://h1.sec.gitlab.net/a/953d2674-ba22-4408-876c-0bd30705f571/image.png) Please note that at this stage the branch `devel` is protected, because no `Developer` can merge a Merge Request or push to it without prior approval from `[@]root` as defined in the `CODEOWNERS` file. 6. Go to `Project Information > Members` and add `[@]inspector` as a member with the role `Developer`. ![image.png](https://h1.sec.gitlab.net/a/9caa3ebf-faa6-46ab-b42f-e0bf513e0992/image.png) ##### Step 2: The attack (attacker context) 1. Clone the repository and create push the malicious tag. ``` git clone git@gitlab.server:gitlab-instance-xxxx/codeowners_bypass.git cd codeowners_bypass git fetch origin # optional git checkout origin/devel -b devel_tag rm CODEOWNERS git add CODEOWNERS git commit -m "attack" git push origin devel_tag:refs/tags/devel ``` At this stage the Code Owners approval rules are disabled. 2. Prepare a branch with malicious content. ``` git checkout origin/devel -b hack echo 1 > malicious_file git add malicious_file git commit -m "add malicious_file" git push origin hack ``` 3. Create a Merge Request from `hack` to `devel` and merge it. ![image.png](https://h1.sec.gitlab.net/a/d9ae69e7-9ec7-4930-bd2b-6efe48d36512/image.png) ![image.png](https://h1.sec.gitlab.net/a/88235803-2447-4dda-985a-b264f62592bb/image.png) 4. Delete the tag to cover your track ``` git push origin -d refs/tags/devel ``` 5. Finally go to the `devel` branch. ![image.png](https://h1.sec.gitlab.net/a/f81f8312-dbcc-403c-a18e-92419420ef2d/image.png) The `malicious_file` is there, the `devel` branch integrity has been compromised ! #### What is the current *bug* behavior? The merge request can be merged since approval becomes optional. ![image.png](https://h1.sec.gitlab.net/a/88235803-2447-4dda-985a-b264f62592bb/image.png) #### What is the expected *correct* behavior? The merge should be blocked since it requires approval from Code Owners. ![image.png](https://h1.sec.gitlab.net/a/13b31688-4716-4093-aec1-398e40468130/image.png) #### Output of checks While tested on a local instance (15.9.2 ) only, it is likely this vulnerability affects GitLab.com as well. #### Impact This can potentially compromise the integrity of the source code hosted on the repository and/or allow the attacker to execute malicious code on the affected systems (developer machines, stealing Gitlab CI secrets, supply chain attack etc...). Please also note that the attack does not require any user interaction from the maintainer of the repository to be carried out. ## Attachments **Warning:** Attachments received through HackerOne, please exercise caution! * [image.png](https://h1.sec.gitlab.net/a/1f4d4419-2793-48a9-983b-1969591f4ca9/image.png) * [image.png](https://h1.sec.gitlab.net/a/23efd73e-6cac-4233-b2ec-75982c78a038/image.png) * [image.png](https://h1.sec.gitlab.net/a/3761cb2e-8103-4633-b75d-ed383fdf3afa/image.png) * [image.png](https://h1.sec.gitlab.net/a/953d2674-ba22-4408-876c-0bd30705f571/image.png) * [image.png](https://h1.sec.gitlab.net/a/9caa3ebf-faa6-46ab-b42f-e0bf513e0992/image.png) * [image.png](https://h1.sec.gitlab.net/a/d9ae69e7-9ec7-4930-bd2b-6efe48d36512/image.png) * [image.png](https://h1.sec.gitlab.net/a/88235803-2447-4dda-985a-b264f62592bb/image.png) * [image.png](https://h1.sec.gitlab.net/a/2a1b3936-db8f-40bc-b9b4-b68e05e12202/image.png) * [image.png](https://h1.sec.gitlab.net/a/f81f8312-dbcc-403c-a18e-92419420ef2d/image.png) * [image.png](https://h1.sec.gitlab.net/a/13b31688-4716-4093-aec1-398e40468130/image.png) ## How To Reproduce Please add [reproducibility information] to this section: 1. 1. 1. [reproducibility information]: https://about.gitlab.com/handbook/engineering/security/#reproducibility-on-security-issues
issue