CSRF on GraphQL API `IntrospectionQuery` operation can run mutation to create gitlab-ci.yml to exfiltrate victim's CI/CD job token
HackerOne report #2473644 by ahacker1
on 2024-04-22, assigned to @ottilia_westerlund:
Report
Summary
There's improper logic used to block mutations on GraphQL mutations on GET requests.
See the code:
https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/controllers/graphql_controller.rb?ref_type=heads
When the query is a GraphQL introspection query (checked with operationName = IntrospectionQuery), it executes execute_introspection_query:
def execute
result = if introspection_query?
execute_introspection_query
else
multiplex? ? execute_multiplex : execute_query
end
render json: result
end
The execute_introspection_query ultimately uses the execute_query function to execute, if not an actual introspection query. Importantly, this executes based on the regular parameters e.g. params[:query]
def execute_query
variables = build_variables(params[:variables])
operation_name = params[:operationName]
GitlabSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
end
However, the code used to check mutation on GET requests. checks the multiplex (_json parameter) first for mutations.
def any_mutating_query?
if multiplex?
multiplex_queries.any? { |q| mutation?(q[:query], q[:operation_name]) }
else
mutation?(query)
end
end
By making the request with both an psuedo introspection query (params[:operationName]= IntrospectionQuery) and a multiplex query, (params[:json] = Array), the controller checks the multiplex parameter for mutations, but actually executes according to the normal query parameter (which remains unchecked for mutations).
Steps to reproduce
Observing that a snippet has been created on your account. (There are other more sensitive actions that can be performed here).
Alternatively, for an AC:Low scenario, an attacker can place this inside a image:
{errors%20clientMutationId%20snippet{webUrl}}}&operationName=IntrospectionQuery&_json[][query]=query%20{__typename})
What is the current bug behavior?
Improper logic used in the graphql controller to allow mutations to be run on GET request with specially crafted payload.
What is the expected correct behavior?
Mutations should be blocked on GET requests correctly.
Relevant logs and/or screenshots
Output of checks
This bug happens on GitLab.com
Results of GitLab environment info
Impact
CSRF, can make victim run arbitary mutations (including those that steal CI/CD variables) with one click.
Note, specifically, the commitCreate mutation can be used to create arbitrary commits on victim Project (including updating the workflow file). Thus, granting access to CI/CD secrets
CVSS justification:
AV: N, this occurs through HTTP requests
AC: Low An attacker can place an image payload of this in Gitlab's markdown, thus infecting notables, issues ... Additionally, since GitLab's cookies are samesite:None, this would also work for any other site that supports images URL (without proxying). So this could be considered the same attack complexity as a stored XSS, Low.
PR: None, An attacker can place the image in other sites, which the victim can visit, without the attacker having an account on GitLab the victim's GitLab instance.
UI: Required, but close to None, as visiting a GitLab issue, or any other popular site/email, can be considered part of their normal workflow.
Scope: Unchanged
Confidentiality: High Through commitCreate, an attacker can update ci/cd workflow file, and steal creds.
Integrity: High commitCreate mutation
Availability: None
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
How To Reproduce
Please add reproducibility information to this section: