Skip to content

Developers can create pipeline schedules on protected branches even if they don't have access to merge

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 #1936572 by js_noob on 2023-04-06, assigned to @ottilia_westerlund:

Report | Attachments | How To Reproduce

Report

Summary

Hello team, as a part of the branch protection rules, developers should not be able to run or schedule pipelines on branches they don't have merge permissions to. From the docs

The schedule owner must have the Developer role. For pipelines on protected branches, the schedule owner must be allowed to merge to the branch.

However, a developer can bypass this condition.

Steps to reproduce

As an owner:

  1. Create a project
  2. Create a new branch called secret-branch
  3. Navigate to https://gitlab.com/OWNER/PROJECT/-/settings/repository#js-protected-branches-settings and set Allowed to merge and Allowed to push and merge to No one image.png
  4. Add a developer to the project

As the developer

  1. Navigate to https://gitlab.com/OWNER/PROJECT/-/pipeline_schedules/new
  2. Fill out all info and set Target branch or tag to secret-branch
  3. Verify that no errors are thrown and the schedule is created successfully
Video/POC

bandicam_2023-04-06_18-26-22-117.mp4

Impact

Developers with no merge access can create pipeline schedules on protected branches.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce

Please add reproducibility information to this section:

Possible solution:

The issue is that:

Therefore, here is a possible solution:

  1. change the Projects::PipelineSchedulesController to use custom authorize action for pipeline schedule instead:

    def new_schedule
      # We need the `ref` here for authorization below
      project.pipeline_schedules.new(ref: params.dig(:schedule, :ref))
    end
    
    def authorize_create_pipeline_schedule!
      return access_denied! unless can?(current_user, :create_pipeline_schedule, new_schedule)
    end
  2. change the Ci::PipelineSchedulePolicy to disable :create_pipeline_schedule when it's a protected branch:

    condition(:protected_ref) do
      ref_protected?(@user, @subject.project, @subject.project.repository.tag_exists?(@subject.ref), @subject.ref_for_display)
    end
    
    rule { custom_protected_ref }.policy do
      prevent :play_pipeline_schedule
      prevent :create_pipeline_schedule
    end

    NOTE: current condition :protected_ref is using @subject.ref to check if the ref is a protected branch, but it should use @subject.ref_for_display. Because ProtectedBranch is created using a ref name e.g. "secret-branch", but Ci::PipelineSchedule is created using a full ref e.g. "refs/heads/secret-branch". Not sure if this is also a bug for Ci::PipelinePolicy as well since Ci::PipelineSchedulePolicy inherits from it.

Edited by Tianwen Chen