Bypassing PAT permissions using glql to gain write permission with read_api scoped personal access token

⚠️ 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 #3451435 by go7f0 on 2025-12-04, assigned to GitLab Team:

Report | Attachments | How To Reproduce

HackerOne Analyst Summary

Summary of the issue

The researcher found personal access token with read_api scope can be escalated with write permission to add commit via POST https://gitlab.com/api/glql with commitCreate query.

Steps to reproduce

  1. As the victim, sign in victim's GitLab account -> Create a private project:

3451435-Step1-victim-create-project.png

  1. As the victim, go to https://gitlab.com/-/user_settings/personal_access_tokens -> Generate a personal access token with read_api scope:

3451435-Step2-victim-create-access-token.png

  1. As the attacker, get victim's access token from step 2

Note: Current attack scenario requires the attacker to obtain victim's read_api scoped personal access token. This increases the Attack Complexity to High.

  1. As the attacker, paste the request below into Burp Repeater:
  • Replace VICTIM_ACCESS_TOKEN with victim's access token from previous step
  • Replace VICTIM_GROUP_NAME with victim's group name
  • Replace VICTIM_PROJECT_NAME with victim's project name
POST /api/glql HTTP/2
Host: gitlab.com
Content-Length: 317
X-Gitlab-Feature-Category: groups_and_projects
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5304.107 Safari/537.36
X-Gitlab-Version: 18.6.1-ee
Content-Type: application/json
Origin: http://gitlab:6789
Referer: http://gitlab:6789/dashboard/projects
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Private-Token: VICTIM_ACCESS_TOKEN 

{
  "query": "mutation { commitCreate(input: { projectPath: \"VICTIM_GROUP_NAME/VICTIM_PROJECT_NAME\", branch: \"glql-read-api-poc2\", startBranch: \"main\", message: \"poc from glql read_api token\", actions:[{ action: CREATE, filePath: \"glql_poc.txt\", content: \"pwned via glql read_api token\" }] }) { errors commit { webUrl } } }"
}
  1. As the attacker, send the above request:

3451435-Step5-attacker-send-request.png

  1. As the victim, refresh project main page. You can see a new commit added:

3451435-Step6-victim-view-file-added.png

Impact statement

If the malicious actor gains the victim's "read_api" personal access token, the attacker can escalate it to write permission and create commit in victim's project.

If you have any questions or concerns about this report, feel free to assign it to H1 Triage via the action picker with a comment indicating your request.

Original Report

Summary

The GLQL interface omitted a scope_validator when constructing the GraphQL context, causing all scope restrictions on PAT to become completely invalid.

Steps to reproduce

Create a new PAT file and select only the minimum scope; here I only selected read_api.

__.png

Accessing http://gitlab:6789/api/v4/personal_access_tokens/self using the generated PAT indeed only has the read_api scope.

__.png

Submitting code using PAT was successful.

__.png

__.png

This example only uses the read_api and code submission; in reality, all user permissions can be obtained through the GLQL interface.

Impact

PAT scope limitations are completely bypassed, granting full user privileges.

Impact

PAT scope limitations are completely bypassed, granting full user privileges.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce

Please add reproducibility information to this section: