"External status checks" can be accepted by users below developer access if the user is either author or assignee of the target merge request
Medium
G
GitLab
Submitted None
Actions:
Reported by
joaxcar
Vulnerability Details
Technical details and impact analysis
### Summary
Any user who is either author or assignee of a merge request can approve that merge request's `external status checks`. This includes users with `Guest` access that creates MR's either through email or through a fork of the project. It also includes users with `Guest` or `Reporter` access getting assigned to an MR, which is not uncommon in public projects.
There exists a tiny overlap with my report [1375376](https://hackerone.com/reports/1375376) which is yet not triaged. I describe this overlap in the end of this summary. The reports look similar, but the vulnerabilities are not related. A fix in 1375376 would not fix this report, only the overlap.
The `external status check` documentation does not offer too much information about how the feature is supposed to function. But the developer discussions and the unit tests suggests that approving an `external status check` should be restricted for users with at least `Developer` access in the project. Here is the issue tracking the development https://gitlab.com/gitlab-org/gitlab/-/issues/267519
In this [thread](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59137#note_567776066) the possibility of users abusing the fact that a status check is not tied to any special token. Rather they use regular PAT's, these discussion mentions
> find_merge_request_with_access will at least mean that only those with developer+ access to the project in question would be able to exploit the feature in this way.
The [unit tests](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/spec/requests/api/status_checks_spec.rb#L29) for this feature checks this assumption with these lines
```
describe 'permissions' do
before do
stub_licensed_features(external_status_checks: true)
end
it { expect { subject }.to be_allowed_for(:maintainer).of(project) }
it { expect { subject }.to be_allowed_for(:developer).of(project) }
it { expect { subject }.to be_denied_for(:reporter).of(project) }
end
```
Validating if the user making the request is developer+.
So to enforce this they have put an authentication block checking if the user have permission to respond to `external status checks` using the function called `find_merge_request_with_access` in this way
```
merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request)
```
Checking the permission `:approve_merge_request` which is enabled for developers. But as it turns out, this permission is also enabled for users with the permission `:update_merge_request`. In https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/policies/merge_request_policy.rb there is this rule
```
rule { can?(:update_merge_request) }.policy do
enable :approve_merge_request
end
```
That enables the permission for anyone that are allowed to update the MR. And in https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/policies/issuable_policy.rb there exists this rule
```
rule { can?(:guest_access) & assignee_or_author }.policy do
enable :read_issue
enable :update_issue
enable :reopen_issue
enable :read_merge_request
enable :update_merge_request
enable :reopen_merge_request
end
```
enabling `:update_merge_request` for anyone that have `:guest_access` and is either assignee or author.
This is probably the root of the problem. And as far as I could make out this is not the intended behavior. A user with `Guest` access can create an MR by forking and directly send approval for all `external status checks` to lure the developers that the MR have been checked. It leads to at least two problems:
* A user with no membership can create a MR in a public project and then "approve" the `external status check's without any membership
* A user who is demoted to `Reporter` in a private project can still "approve" `external status check's in MR's where the user is either author or assignee
and at the moment thanks to the vulnerability that I have reported in [1375376](https://hackerone.com/reports/1375376) at present it is also possible to:
* A user who is demoted to `Guest` in a private project can still "approve" `external status check's in MR's where the user is either author or assignee while not being able to actually view the MR
### Steps to reproduce
External status checks is an `Ultimate` feature, so make sure the project is created in such an environment
1. Create two users `owner01` and `guest01`
2. Log in as `owner01` and create a public project `project01` by visiting https://gitlab.com/projects/new#blank_project and take a note of the project ID
3. Go to the project settings page and expand the tab `merge requests` and scroll down to `external status checks`, settings page https://gitlab.com/owner01/project01/edit
4. Create a status check with any name and endpoint, and leave the
5. Log out and log in as `guest01`
6. Go to the project page https://gitlab.com/owner01/project01 and create a fork with the `fork` button, call it `fork01`.
7. When the fork is created, create a new branch in the fork https://gitlab.com/guest01/fork01/-/branches/new called `new_branch`
8. When the fork is created directly click on the option "create a merge request", in the "New merge request" page click `Change branches` and select the target branch as any branch on the original `project01`
9. Click "Create" and a new MR should be created in `project01` (this is a guest contribution and a normal open-source flow, but note that the `guest01` user is NOT a member of `project01`)
10. Go to https://gitlab.com/-/profile/personal_access_tokens and create an access token for the API for `guest01`
11. Open a terminal and make this request to get the ID of the status check (user `project01` ID and MR IID which is probably 1 and `guest01` token), take a note of the returned ID of the status check
```
curl "https://gitlab.███/api/v4/projects/<PROJECT_ID>/merge_requests/<MR_IID>/status_checks" -H "Authorization: Bearer <TOKEN>"
```
12. Send this request to check for the SHA, the request will fail with a message telling you which SHA to use, in this request we use a dummy SHA=a (make sure to also replace CHECK_ID to the found ID from step 12)
```
curl --request POST \
--url 'https://gitlab.com/api/v4/projects/<PROJECT_ID>/merge_requests/<MR_IID>/status_check_responses?sha=a&external_status_check_id=<CHECK_ID>' \
--header 'Authorization: Bearer <TOKEN>'
```
13. Now use the returned SHA in this request to finally "approve" the status check for the MR
```
curl --request POST \
--url 'https://gitlab.domain.com/api/v4/projects/<PROJECT_ID>/merge_requests/<MR_IID>/status_check_responses?sha=<SHA>&external_status_check_id=<CHECK_ID>' \
--header 'Authorization: Bearer <TOKEN>'
```
14. Go to the MR page and verify that the status check is now green and checked, https://gitlab.com/owner01/project01/-/merge_requests/1
### Impact
A `Guest` user can send acknowledge messages to "approve" `external status checks` on MR's where the user is either author or assignee. This makes it possible for a malicious user to "spoof" acceptance of MR's in projects where the user should not be able to do this. In public projects this mean that any guest contribution from non-members can have its `external status checks` checked by the author itself even if not a member of the project.
### What is the current *bug* behavior?
Users with access level below `Developer` can accept `external status checks` if they are either author or assignee of the MR
### What is the expected *correct* behavior?
Only `Developer`+ users that are members of the project should be able to user their PAT to "approve" the `external status check`
### Output of checks
This bug happens on GitLab.com
## Impact
A `Guest` (or `Reporter`) user can send acknowledge messages to "approve" `external status checks` on MR's where the user is either author or assignee. This makes it possible for a malicious user to "spoof" acceptance of MR's in projects where the user should not be able to do this. In public projects this mean that any guest contribution from non-members can have its `external status checks` checked by the author itself even if not a member of the project.
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Bounty
$610.00
Submitted
Weakness
Improper Access Control - Generic