`Limit access to this project` setting is not applicable to CI_JOB_TOKEN of `gitlab-security-policy-bot`
HackerOne report #2575051 by yvvdwf
on 2024-06-26:
Report
Hello,
The following code is vulnerable:
### 1. app/services/merge_requests/merge_service.rb#L103-122
def after_merge
...
if delete_source_branch?
MergeRequests::DeleteSourceBranchWorker.perform_async([@]merge_request.id, [@]merge_request.source_branch_sha, branch_deletion_user.id)
end
...
end
def branch_deletion_user
[@]merge_request.force_remove_source_branch? ? [@]merge_request.author : current_user
end
### 2. app/workers/merge_requests/delete_source_branch_worker.rb
class MergeRequests::DeleteSourceBranchWorker
...
def perform(merge_request_id, source_branch_sha, user_id)
...
::MergeRequests::RetargetChainService.new(project: merge_request.source_project, current_user: user)
.execute(merge_request)
...
- When a merge request is merged and it was marked as
Delete source branch when merge request accepted
, then the source branch will be deleted by the merge request author, not by the one who merged the request. - Before deleting the source branch, Gitlab tries to re-target any other merge requests that use the deleted branch as its target.
- These re-targeting actions end up to refresh these merge requests, and to trigger new pipelines for them
- These pipelines are usually omitted, but if their
only:refs
ismerge_requests
then they will be executed - Gitlab keeps owners of a merge request if it is imported via direct transfer, such as, via an external source like GitHub, Bitbucket, or another instance of GitLab, by mapping their emails.
Consequently, an attacker can execute pipeline jobs as arbitrary user.
Gitlab recently limits the scope of CI_JOB_TOKEN
which can not be used to access to other projects unless users explicitly configures their projects to allow it to access to. However gitlab-security-policy-bot
(again) has a specific permission:
rule { security_policy_bot }.policy do
enable :create_pipeline
enable :create_bot_pipeline
enable :build_download_code
end
This means that we can abuse this bot to access to its original project and execute CI/CD pipeline on that project.
In brief:
- if a project
- is public, regardless its repository is set as public -
Everyone With Access
or private -Only Project Members
-
and has a
security-policy-bot
member
- is public, regardless its repository is set as public -
- then attackers can
- access to its repository (if it is private) and its CI variables
- trigger its CI/CD pipeline, thus, control its environments/deployments, its packages, etc
Reproduce
As Gitlab.com currently disables the direct transfer feature, the following steps are to run in Gitlab self-management instances.
To reproduce, we need 2 Gitlab instances:
-
The first Gitlab instance is the main instance which contains the victim project and is accessible via
http://gl.lo
- please ensure it has
Ultimate
license - please ensure it has at least one Gitlab runner
- please ensure it has
-
The second Gitlab instance is to create a dummy group which will be imported to the main one. This instance is fully controlled by the attacker (who is a normal user in the first instance). This instance should be in a server with a public IP though that the main Gitlab instance above can access to.
http://gl.lo, then:
As victim, go to- create a public repository, note its ID, e.g.,
22222
- add
.gitlab-ci.yml
file within the following content:
test:
script:
- echo Hello world
-
go to
Settings / General / Visibility, project features, permissions
:-
Project visibility
:Public
-
Repository
:Only Project Members
- click
Save changes
button
-
-
go to
Secure / Policies
and set up a new policy for it. -
go to
Manage / Members
, you should see a new memberGitlab Security Policy Bot
. Note its username and ID, e.g.,BOT_USERNAME
,BOT_ID
, we obtain its email in the following form$BOT_ID-$BOT_USERNAME@users.noreply.gl.lo
(please replaceBOT_USERNAME
andgl.lo
by suitable values), , e.g.,18-gitlab_security_policy_project_72_bot_e15f68e4886390cc996769f9b0c935ca@users.noreply.gl.lo
As attacker
Step 0. Prepare
This step is done in the second Gitlab instance which is fully controlled by the attacker
- login this instance as admin,
- go to
Admin Area
:- go to
Settings / General / Import and export settings
, enableAllow migrating GitLab groups and projects by direct transfer
, then clickSave changes
- go to
Overview / Users
, then change Administrator's email to the bot's email
- go to
- go to
Edit Profile
to set this email asPublic email
- go to
Access Tokens
, then create a new personal accessYOUR_GITLAB_API_TOKEN
token withapi
scope - create a new group
group-x
, then add a new blank project,test
, within a README:- modify README.md, commit it to a new branch
a
, then create a merge request froma
tomain
(please make sureDelete source branch when merge request is accepted.
is checked) - open the branch
a
, add a.gitlab-ci.yml file, then commit it to a new branch
b, then create a merge request from
bto
a`:- Filename:
.gitlab-ci.yml
- File content (Note: please replace
22222
by the ID of victim's project we created above)
- Filename:
- modify README.md, commit it to a new branch
hi:
image: ruby:3.1
only:
refs:
- merge_requests
script:
- echo "This script is created by attacker, run by $GITLAB_USER_LOGIN"
- curl -X POST "$CI_API_V4_URL/projects/22222/trigger/pipeline?token=$CI_JOB_TOKEN&ref=main&&variables%5BBASH_ENV%5D=%24%28echo%20%3D%3DHello-from-Attacker%20%3E%20%2Fdev%2Fstderr%29"
+ Target Branch: `b` (_Note_: Upon modified `Target Branch` value to `b`, you will see a checkbox `Start a new merge request with these changes`, let it be checked)
+ click `Commit changes`
+ After clicking on the button `Commit changes`, you are redirected to a merge request page. Click `Create merge request` button to create a merge request from branch `b` to `a`.
Step 1. Attack
This step is done in the first Gitlab instance
- click ' + / New group / Import group
, then fill the form, then click
Connect instance`-
GitLab source instance base URL
is the url of the instance we created in Step 0 -
Personal access token
is the tokenYOUR_GITLAB_API_TOKEN
we created in Step 0
-
- import group
group-x
- after importing successfully, you should see
group-x/test
project which has 2 merge requests created byBOT_USERNAME
- open the merge request from
a
tomain
, then clickMerge
button. - open the list of pipeline jobs, you should see a new job that was triggered by the
BOT_USERNAME
but using the.gitlab-ci.yml
file we created previously in the branchb
. - Open the victim project, you should see a job which was triggered also by
BOT_USERNAME
and its execution is controlled by attacker viaBASH_ENV
variable.
Impact
This vulnerability allow an attacker to execute pipelines as arbitrary user. By abusing Gitlab Security Policy Bot
, attackers can
- access to private repositories and CI/CD variables (
C:H
) - trigger CI/CD pipelines, thus control deployments/environments/packages/... (
I:H
,S:C
)
Best regards,
yvvdwf
Impact
This vulnerability allow an attacker to execute pipelines as arbitrary user. By abusing Gitlab Security Policy Bot
, attackers can
- access to private repositories and CI/CD variables (
C:H
) - trigger CI/CD pipelines, thus control deployments/environments/packages/... (
I:H
,S:C
)
How To Reproduce
Please add reproducibility information to this section: