Blocked User Can Still Access Gitlab Instance Through Graphql Subscription via WebSocket connection

⚠️ 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 #3049150 by rogerace on 2025-03-20, assigned to @ottilia_westerlund:

Report | Attachments | How To Reproduce

Report

Summary

Gitlab allows an administrator to block a user, and the blocked user cannot login or access any data in the gitlab instance anymore. However, there is a bypass. A blocked user can still use his PAT and Graphql Subscription to leak information from the instance. The leaked data contains

  1. issues update, including title, description (through queries like workitemUpdated), metadata (assignee, weight, label through queries like issuableAssigneesUpdated), and new issues.
  2. merge request status update through queries like mergeRequestReviewersUpdated
  3. ci pipeline status update through queries like ciPipelineStatusUpdated

Basically every data that could be queried through gitlab graphql's subscription can be leaked, which is a lot. The reason is that this bypass is not on specific graphql queries, but on the Graphql subscription genre. So all queries allowed in graphql subscription can be executed by this blocked user, leaking all data allowed in these queries.

Note

This report is on ==Blocked== user, which is different from ==Banned== user and ==IP Restricted== user, which I also found various bypasses and reported in my other reports. ==They are not the same and not duplicate==.

# Technical Details

The reason for this vuln is quite straightforward. When a user is blocked, gitlab would prevent it form access_api, and this access_api policy is checked in graphql controller. However, graphql subscription does not use graphql controller as it uses action cable to open and remain websocket connection, totally bypass this access control against blocked user, leading to this problem.

Steps to reproduce

==The step to reproduce for this vuln is a little complicated. Please make sure following my reproduction steps exactly and refer to my video PoC if necessary.==
I will use one graphql subscription issuableAssigneesUpdated to demonstrate this vulnerability, but it works for any graphql subscription allowed by gitlab.

  1. In your own self-managed gitlab, using your root account, create a group and a project and add a user (attacker) to this group.
  2. Create an issue in the project, give it any name.
  3. In another browser, login the attacker account, create a Personal Access Token and save it for future uses.
  4. Using burpsuite to proxy all following request in the attacker browser.
  5. Using the attacker account, go to the issue created in step 2.
  6. In your burpsuite, you will find a request to /cable, go to the WebSockets history tab, you will find many messages, find a to server message with content looking like
    {"command":"subscribe","identifier":"{\"channel\":\"GraphqlChannel\",\"query\":\"subscription issuableAssigneesUpdated. Send this request to repeater. Note it must have ==subscription issuableAssigneesUpdated==.
  7. Now, using the root account, go to admin panel, find the attacker account, and block it by clicking on the ellipsis icon on the right of edit button.
  8. Now in the attacker browser, if you refresh, you will find you are blocked.
  9. Go back to the burpsuite repeater, on the top middle, you will find a toggle button. If that button is still on, toggle it off.
  10. Toggle that button on, and you will find you can still connect, but if you send the issuableAssigneesUpdated websocket message again, you will find you are unauthorized by checking the message log on the right side. ==Leave the toggle on.==
  11. Now, on the right side of toggle button, there is a pencil, click that pencil, you will find many websocket to choose, choose the connection representing your gitlab instance, and on the bottom, there is a clone button, click on that.
  12. You will see a panel, with HTTP request for you to edit, delete your gitlab session cookie in the Cookie header, and add PRIVATE-TOKEN: {YOUR PAT} as a header in the request. And then click on the connect button.
  13. You will find you successfully reconnected, and if you re-send the issuableAssigneesUpdated graphql message again, you will see it is successful. The response is no longer unauthorized subscription, but null, which is expected because it would only show data when there is update.
  14. Go back to the owner account, update the issue title.
  15. Go back to burpsuite, scroll through the messages, you will see the update.
PoC

<redacted>

Impact

Confidentiality: High.
The vulnerability allows a blocked user to bypass access controls and retrieve sensitive information through GraphQL subscriptions. This includes:

  1. Issues: Titles, descriptions, metadata (assignees, weights, labels), and new issues.
  2. Merge Requests: Status updates, including approvals and changes.
  3. CI Pipelines status updates

Since GraphQL subscriptions can be used to monitor real-time updates, the blocked user can continuously receive updates on any changes to the data they are subscribed to, effectively leaking a wide range of sensitive information.

PR: None since this user is effectively blocked.
UI: None. This attacker does not need victim to do anything special other than continue using the gitlab instance.

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

  • <redacted>

How To Reproduce

Please add reproducibility information to this section:

3.

Edited by ADandy