Last pipeline of any merge request leaked to unauthorized users
HackerOne report #560484 by xanbanx
on 2019-05-02, assigned to asaba
:
Summary
GitLab has the feature to disable pipelines for non-project members on public project. The intend here is that only project members are granted to access the pipeline information. In a recent version, GitLab added the pipeline information for the last pipeline to a merge request API. However, here proper authentication is missing. This allows any users, who is not not member of the project but can access the merge request also to read the last pipeline information of this merge request.
Steps to reproduce
This was tested on gitlab.com, 11.10.4-ee
- Create a public project, set Pipelines visibility to project members only, and disable public pipelines
- Push something to the repo such that CI is working, e.g., just the BASH
gitlab-ci.yml
example file - Push some changes and create a merge request
- As a non-project member execute the following API call:
curl --header "PRIVATE-TOKEN: <your_access_token>" https://example.gitlab.com/api/v4/projects/<project-id>/merge_requests/1
Since merge requests are available for public users, this will return a JSON response like the following:
{
"id": 123456,
"iid": 1,
"project_id": 1,
"title": "Add new file",
"description": "",
"state": "opened",
"created_at": "2019-05-02T11:16:48.737Z",
"updated_at": "2019-05-02T11:16:48.737Z",
"merged_by": null,
"merged_at": null,
"closed_by": null,
"closed_at": null,
"target_branch": "master",
"source_branch": "new-test-branch",
"user_notes_count": 0,
"upvotes": 0,
"downvotes": 0,
"author": {
"id": 1,
"name": "tester",
"username": "root",
"state": "active",
"avatar_url": "https://secure.gravatar.com/avatar/6449f73ed21abd1624dee923921e9176?s=80&d=identicon",
"web_url": "https://example.gitlab.com/root"
},
"assignee": null,
"source_project_id": 1,
"target_project_id": 1,
"labels": [],
"work_in_progress": false,
"milestone": null,
"merge_when_pipeline_succeeds": false,
"merge_status": "can_be_merged",
"sha": "74256392f7575f62639236a42ac0469c43972e3f",
"merge_commit_sha": null,
"discussion_locked": null,
"should_remove_source_branch": null,
"force_remove_source_branch": true,
"reference": "!1",
"web_url": "https://example.gitlab.com/root/test-project/merge_requests/1",
"time_stats": {
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": null,
"human_total_time_spent": null
},
"squash": false,
"subscribed": false,
"changes_count": "1",
"head_pipeline": {
"id": 2,
"sha": "74256392f7575f62639236a42ac0469c43972e3f",
"ref": "new-test-branch",
"status": "success",
"web_url": "https://example.gitlab.com/root/test-project/pipelines/2",
"before_sha": "0000000000000000000000000000000000000000",
"tag": false,
"yaml_errors": null,
"user": {
"id": 1,
"name": "tester",
"username": "root",
"state": "active",
"avatar_url": "https://secure.gravatar.com/avatar/6449f73ed21abd1624dee923921e9176?s=80&d=identicon",
"web_url": "https://example.gitlab.com/root"
},
"created_at": "2019-05-02T11:16:50.264Z",
"updated_at": "2019-05-02T11:21:40.466Z",
"started_at": "2019-05-02T11:20:15.270Z",
"finished_at": "2019-05-02T11:21:40.373Z",
"committed_at": null,
"duration": 76,
"coverage": null,
"detailed_status": {
"icon": "status_success",
"text": "passed",
"label": "passed",
"group": "success",
"tooltip": "passed",
"has_details": false,
"details_path": "/root/test-project/pipelines/2",
"illustration": null,
"favicon": "https://example.gitlab.com/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
}
},
"diff_refs": {
"base_sha": "95411457e54f619aa861cbb1dbd7c505bc767ca2",
"head_sha": "74256392f7575f62639236a42ac0469c43972e3f",
"start_sha": "95411457e54f619aa861cbb1dbd7c505bc767ca2"
},
"merge_error": null,
"user": {
"can_merge": false
},
"approvals_before_merge": 0
}
Here, you notice that response field head_pipeline
, which gives you information about the last pipeline run. However, as pipelines are disabled for public users, this is a violation of the authentication model.
Impact
I added the impact. below. Maybe you can remove this from the template here, since H1 already has a dedicated impact section at the end, which is mandatory.
Examples
This API call is user for example when rendering the related merge request tab on an issue. Here, the pipeline information of the merge request is rendered for unauthorized users, which do not have access to Pipelines in general.
Output of checks
This bug happens on GitLab.com
Steps to mitigate
When looking at lib/api/entities.rb
, the MergeRequest
entity is unconditionally exposing the head pipeline using expose :head_pipeline, using: 'API::Entities::Pipeline'
. Here, an authorization check for read_pipeline
is missing.
Impact
Unauthorized users, who can read merge request have access to the pipeline information of this merge request. Any user, who does have merge request permission can iterate over all merge requests and can query the pipeline information.