Loading HuntDB...

IDOR in "external status check" API leaks data about any status check on the instance

Medium
G
GitLab
Submitted None
Reported by joaxcar

Vulnerability Details

Technical details and impact analysis

Insecure Direct Object Reference (IDOR)
### Summary The API endpoint for returning approval from an `external status check` contains an IDOR that lets a user list information about all `external status checks` on the GitLab instance. The feature is an `Ultimate` feature, but can be accessed by starting an `Ultimate` trial on GitLab.com. So the attack is possible with a regular account. When an `external status check` is configured, the project will send out information about MRs to a specified endpoint. This endpoint can then be configured to answer to the request to "pass" the status check. Description of the feature [here](https://docs.gitlab.com/ee/user/project/merge_requests/status_checks.html). The API endpoint for this answer is ([info](https://docs.gitlab.com/ee/api/status_checks.html#set-status-of-an-external-status-check)) ``` POST /projects/:id/merge_requests/:merge_request_iid/status_check_responses ``` with the passed in parameters `sha` and `external_status_check_id`. It is the later one that contains the IDOR. This parameter tells GitLab which `external status check` the request is targeting. And the answer back from GitLab is JSON containing info about the MR but also info about the status check configuration. By altering the ID sent, a user can obtain info about any status check on the instance (even from Private projects). Leaked information about a status check could look like the example below and could contain: * Private project name and ID * Name of status check * Private address to external status check tool * Name and ID of protected branch connected to the status check * Access rules to protected branch, if configured also name of user that is allowed to access ``` "external_status_check": { "id": 10, "name": "Name of status check", "project_id": 33, "external_url": "https://victim.service.com", "protected_branches": [ { "id": 24, "name": "Name of protected branch", "push_access_levels": [ { "access_level": 40, "access_level_description": "Name of user with access to protected branch", "user_id": 2, "group_id": null }, { "access_level": 30, "access_level_description": "Developers + Maintainers", "user_id": null, "group_id": null } ], "merge_access_levels": [ { "access_level": 30, "access_level_description": "Developers + Maintainers", "user_id": null, "group_id": null }, { "access_level": 40, "access_level_description": "Name of user with access", "user_id": 2, "group_id": null } ], "allow_force_push": true, "unprotect_access_levels": [], "code_owner_approval_required": true } ``` ### Steps to reproduce 1. Create two users `victim01` and `attacker01` 2. Log in as `victim01` and create a new PRIVATE project called `victim_project` at https://gitlab.domain.com/projects/new#blank_project 3. Go to go to project settings https://gitlab.domain.com/victim01/victim_project/edit and expand "Merge request" under "General". Scroll down to "Status checks" and click create new. 4. Name the status check "Victim status check" and enter a API endpoint "https://victim.hidden.com" 5. Click save 6. Log out and log back in as `attacker01` 7. Go through step 2 to 5 again but name the project `attacker_project` and the status check anything. Take a note of the ID of the project. We will call it `attackID` 8. Now create a new branch in `attacker_project` at https://gitlab.domain.com/attacker01/attacker_project/-/branches/new 9. Click "Create new merge request" when the branch is created. Name the MR anything and click create 10. Go to https://gitlab.domain.com/-/profile/personal_access_tokens and create an access token for `attacker01`, we will call it `TOKEN` 11. Open a terminal and make this request (the SHA is just "a" here, we will get the correct one as a response) ``` curl --request POST \ --url 'https://gitlab.domain.com/api/v4/projects/<ATTACKID>/merge_requests/1/status_check_responses?sha=a&external_status_check_id=2' \ --header 'Authorization: Bearer <TOKEN>' ``` 12. This iniitial request will return an error saying `the correct sha is <SHA>` 13. Now send with correct SHA ``` curl --request POST \ --url 'https://gitlab.domain.com/api/v4/projects/<ATTACKID>/merge_requests/1/status_check_responses?sha=<SHA>&external_status_check_id=2' \ --header 'Authorization: Bearer <TOKEN>' ``` 14. We will get a response with info about the MR and in the end info about the `status check` 15. Now send the same request again but change status check id to 1 ``` curl --request POST \ --url 'https://gitlab.domain.com/api/v4/projects/<ATTACKID>/merge_requests/1/status_check_responses?sha=<SHA>&external_status_check_id=1' \ --header 'Authorization: Bearer <TOKEN>' ``` 16. The same MR info is returned, but in the end there will be info about the private status check from `victim_project` ### Impact An attacker can leak information about any `external status check` on the instance. This data contains sensitive private information. ### What is the current *bug* behavior? The parameter `external_status_check_id` is not restricted to the status checks configured in the project. ### What is the expected *correct* behavior? The parameter should only allow IDs that corresponds to status checks in the target project. ### Output of checks This bug happens on GitLab.com ## Impact Leaked information about a status check could look like the example below and could contain: * Private project name and ID * Name of status check * Private address to external status check tool * Name and ID of protected branch connected to the status check * Access rules to protected branch, if configured also name of user that is allowed to access

Report Details

Additional information and metadata

State

Closed

Substate

Resolved

Bounty

$610.00

Submitted

Weakness

Insecure Direct Object Reference (IDOR)