Skip to content

Run pipeline as project owner using container registry scanning

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 #2649798 by pwnie on 2024-08-11, assigned to H1 Triage:

Report | How To Reproduce

Report

Summary

A user can run pipelines as the project owner when container registry vulnerability scanning is enabled. This only affects projects in user namespaces (not within a group). The reason for this is this section of code in ee/app/models/ee/container_registry/event.rb:

 user_originator = originator.is_a?(User) ? originator : project.owner # This line here

        return unless user_originator

        push_event = ::ContainerRegistry::ImagePushedEvent.new(  
          data: { project_id: project.id, user_id: user_originator.id, image: image_path }).tap do |event|  
          event.project = project  
        end  

This code is triggered when a container registry push occurs:


      desc 'Receives notifications from the container registry when an operation occurs' do  
        detail 'This feature was introduced in GitLab 12.10'  
        consumes [:json, DOCKER_DISTRIBUTION_EVENTS_V1_JSON]  
        success code: 200, message: 'Success'  
        failure [  
          { code: 401, message: 'Invalid Token' }  
        ]  
        tags %w[container_registry_event]  
      end

      # This endpoint is used by Docker Registry to push a set of event  
      # that took place recently.  
      post 'events' do  
        params['events'].each do |raw_event|  
          event = ::ContainerRegistry::Event.new(raw_event) #This line here  

When the event is handled, an internal event queue is pushed to:

        ::Gitlab::EventStore.publish(push_event)  
store.subscribe ::AppSec::ContainerScanning::ScanImageWorker,  
            to: ::ContainerRegistry::ImagePushedEvent,  
            delay: 1.minute,  
            if: ->(event) { ::AppSec::ContainerScanning::ScanImageWorker.dispatch?(event) }  

Since an image push can be authenticated with a deploy token, the project.owner fallback will be triggered. This means its trivial to run a pipeline as the owner. To elaborate on why the vulnerability doesnt work on group projects, it's because the owner method returns the parent namespace when its within a group, but the user when its in a user namespace. This is kind of weird and theres a comment in the code talking about deprecating this.

To perform this you need the following prerequisites:

  1. A victim user with a GItlab Ultimate subscription to use https://docs.gitlab.com/ee/user/application_security/container_scanning/#container-scanning-for-registry
  2. Container registry enabled on your instance, making it far easier to demonstrate this on Gitlab.com. Container registries are a hassle to setup if you're just a HackerOne triager looking to validate this. I personally haven't even validated this at run time since I dont have an Ultimate subscription, though I am 99.99% certain this will work as intended since I've read the code thoroughly. Unfortunately I cant provide a video PoC but its fairly straightforward to test.
  3. An attacker user with maintainer privileges in the victim project

Steps to reproduce

  1. With your victim user account (with an Ultimate subscription), create a project in your user namespace and enable Container Scanning for Registry. You can find instructions on how to enable it here: https://docs.gitlab.com/ee/user/application_security/container_scanning/#container-scanning-for-registry
  2. Create an attacker account and grant them the maintainer role in the project
  3. As the attacker create a deploy token, you can find instructions on how to do so here: https://docs.gitlab.com/ee/user/project/deploy_tokens/
  4. Ensure you have docker installed the docker CLI is available on your system. Theres extensive documentation online on how to do so.
  5. Run the following commands:
docker login -u YOUR_DEPLOY_TOKEN_USERNAME -p YOUR_DEPLOY_TOKEN_PASSWORD registry.gitlab.com  
docker pull alpine:latest  
docker tag alpine:latest 'registry.gitlab.com/VICTIM_USERNAME/VICTIM_PROJECT/image:latest'  
docker push 'registry.gitlab.com/VICTIM_USERNAME/VICTIM_PROJECT/image:latest'                   

This will login to the registry, pull alpine from Docker Hub (its a small image), tag it with a new image name, and push it to the registry.
6. Go to the victim project and observe a pipeline is running as the owner of the project

Impact

Run pipelines as owner of project when maintainer privileges are held, the project is in a user namespace, and has an Ultimate subscribed owner.

Components affected

Gitlab

Impact

Run pipelines as owner of project when maintainer privileges are held, the project is in a user namespace, and has an Ultimate subscribed owner.

How To Reproduce

Please add reproducibility information to this section: