Cache for protected branches is not isolated and can be used to gain code execution on future jobs.
**[HackerOne report #1182375](https://hackerone.com/reports/1182375)** by `wapiflapi` on 2021-05-02, assigned to @ankelly:
[Report](#report) | [Attachments](#attachments) | [How To Reproduce](#how-to-reproduce)
## Report
##### Summary
Citing https://docs.gitlab.com/ee/ci/caching/
> GitLab CI/CD provides a caching mechanism that can be used to save time when your jobs are running. [...] The most common use case of caching is to avoid downloading content like dependencies or libraries repeatedly between subsequent runs of jobs.
The `protected` status of the branch on which the pipeline is running is not taken into account. This means that a user with `developer` permissions, or any permissions allowing them to run a pipeline, could poison the cache and backdoor those "dependencies or libraries", and get code execution in the context of a `protected` branch and even in the context of the final build that will be deployed to other environments.
##### Steps to reproduce
1. Set-up a gitlab project with the following `.gitlab-ci.yml` file on the `master` branch which is protected by default.
```yaml
cache:
# This is recommended in https://docs.gitlab.com/ee/ci/caching/
# It is considered "safe from accidentally overwriting the cache".
key: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
paths:
- critical_binary
stages:
- test
test:
stage: test
image: ubuntu
script:
- echo "Building critical_binary if it isn't cached."
- if [ ! -f critical_binary ]; then echo 'echo critical_binary is safe' > critical_binary; else echo "Reusing cached critical_binary."; fi;
- echo "Executing critical_binary."
- sh ./critical_binary
```
2. Observe that the pipeline ran successfully and that it contains `critical_binary is safe`.

3. Create a new branch `feature/cache-poisoning`, edit the `.gitlab-ci.yml` file to the following:
```yaml
cache:
# If the key is random it would still be possible to poison the cache
# once the pipeline started because it shows the key. It would
# become a race condition, it's probably easy to inject in
# between long running jobs.
key: "test-master" # Easy to guess this one.
paths:
- critical_binary
stages:
- test
test:
stage: test
image: ubuntu
script:
- echo "Overwriting critical_binary with evil code."
- echo 'echo critical_binary is evil' > critical_binary
```
4. Observe that the pipeline for `feature/cache-poisoning` ran successfully and it contains `Overwriting critical_binary with evil code.`

5. Start a new pipeline for `master`, observer that it now outputs `critical_binary is evil`, showing that its cache has been poisoned and arbitrary code execution on `protected` branch has been achieved by a user with only the privileges to run a pipeline for the `unprotected` branch `feature/cache-poisoning`.

##### Impact
If a user has enough privilege to push code and trigger a pipeline on any branch (eg: they are a `developer`), then they can poison the cache for a protected branch and execute arbitrary code. This elevates their privilege considerably. For example:
- A developer could leak sensitive information from protected environnement variables. (deploy tokens, registry access, AWS tokens, etc.)
- A developer could alter the build process and backdoor the final product without detection because they wouldn't be touching the git repo.
##### Examples
In addition to the previous explanation of how to reproduce this, I set-up a demo project on gitlab.com.
Relevant URLs:
- The project: https://gitlab.com/wapiflapi/gl-cache-poisoning/
- job on master before poisoning: https://gitlab.com/wapiflapi/gl-cache-poisoning/-/jobs/1229691941
You can see `critical_binary is safe`.
- job on feature/poison triggered by a "developer": https://gitlab.com/wapiflapi/gl-cache-poisoning/-/jobs/1229693625
You can see `Overwriting critical_binary with evil code.`
- New pipeline on master after the poisoning: https://gitlab.com/wapiflapi/gl-cache-poisoning/-/jobs/1229693780
Now we see `critical_binary is evil` without any code changes on the `protected` `master` branch.
##### What is the current *bug* behavior?
Jobs running for an unprotected branch can overwrite the cache for a those running on a protected branch.
##### What is the expected *correct* behavior?
Jobs running for an unprotected branch should not be able to influence those running on a protected branch.
Cache for protected and unprotected branches should be separate. Maybe cache keys should have a forced prefix indicating the privilege with which the job is running.
##### Relevant logs and/or screenshots
N/A
##### Output of checks
This bug happens on GitLab.com
#### Impact
(Copied from Impact in summary.)
If a user has enough privilege to push code and trigger a pipeline on any branch (eg: they are a `developer`), then they can poison the cache for a protected branch and execute arbitrary code. This elevates their privilege considerably. For example:
- A developer could leak sensitive information from protected environnement variables. (deploy tokens, registry access, AWS tokens, etc.)
- A developer could alter the build process and backdoor the final product without detection because they wouldn't be touching the git repo.
## Attachments
**Warning:** Attachments received through HackerOne, please exercise caution!
* [Screenshot_from_2021-05-02_11-17-51.png](https://h1.sec.gitlab.net/a/824d48cb-9553-4f8b-bbbe-1b03f9c8ac8b/Screenshot_from_2021-05-02_11-17-51.png)
* [Screenshot_from_2021-05-02_11-19-44.png](https://h1.sec.gitlab.net/a/aef2fb4c-fc3f-4eab-9911-2ade220a4d09/Screenshot_from_2021-05-02_11-19-44.png)
* [Screenshot_from_2021-05-02_11-20-45.png](https://h1.sec.gitlab.net/a/66346b4f-3f9c-40f0-8167-a978c973a077/Screenshot_from_2021-05-02_11-20-45.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