
CVE-2026-85706 के लिए Python PoC exploit, जो Workhorse path-encoding bypass के माध्यम से GitLab CE/EE में unauthenticated arbitrary file read है, साथ में writeup और bypass variants।
GitLab CE/EE में अनधिकृत मनमाना लोकल फ़ाइल रीड। 18.7–19.1.7, 19.2.0–19.2.5, 19.3.0–19.3.1 को प्रभावित करता है। 19.1.8 / 19.2.6 / 19.3.2 (2026-09-10) में ठीक किया गया। CVSS 10.0, कथित तौर पर जंगल में शोषण किया गया। मूल रिपोर्ट s3ntago द्वारा और यह रेपो सिर्फ़ मेरा writeup + PoC है।
यह PoC केवल शैक्षिक और रक्षात्मक शोध उद्देश्यों के लिए प्रकाशित किया गया है, ताकि एडमिन और शोधकर्ता इस भेद्यता को समझ सकें और इसका परीक्षण कर सकें। इसे विशेष रूप से उन सिस्टमों के विरुद्ध चलाएँ जिनके आप मालिक हैं या जिनके परीक्षण के लिए आपके पास स्पष्ट लिखित प्राधिकरण है। यदि आपका इंस्टेंस प्रभावित रेंज में आता है, तो पढ़ना बंद करें और पहले 19.1.8 / 19.2.6 / 19.3.2 पर पैच करें।
तीन रिपॉज़िटरी एंडपॉइंट (POST :id/repository/commits, POST/PUT :id/repository/files/:file_path) Workhorse के requestBodyUploader के पीछे बैठे हैं। Rails हैंडलर कच्चे file.path रिक्वेस्ट फ़ील्ड से सीधे डिस्क पर मौजूद पाथ पढ़ता है और किसी भी प्रमाणीकरण से पहले उसे File.open करता है। प्रमाणीकरण का काम नहीं करता क्योंकि Workhorse का signing round-tripper हर उस रिक्वेस्ट पर एक वैध JWT संलग्न करता है जिसे वह प्रॉक्सी करता है, इसलिए जो कुछ भी generic API proxy तक पहुँचता है वह उस जाँच को पास कर लेता है।
require_gitlab_workhorse!Gitlab-Workhorse-Api-Requestयह सबके लिए तत्काल LFI न होने का एकमात्र कारण यह है कि Workhorse को पहले रिक्वेस्ट को फिर से लिखना चाहिए। लेकिन इसका route regex escaped path (EscapedPath() के साथ-साथ एक path.Clean क्लोन जो कभी %XX को डिकोड नहीं करता) के विरुद्ध मैच करता है, जबकि Puma Grape रूटिंग से पहले %XX को डिकोड करता है। इसलिए किसी स्टैटिक सेगमेंट के किसी भी अक्षर को percent-encode करें (%63ommits, %72epository, %66iles), एक ट्रेलिंग स्लैश जोड़ें, या .json लगाएँ जिसे Workhorse का regex मिस कर देता है, Rails अभी भी भेद्य हैंडलर तक रूट करता है। यही एन्कोडिंग बेमेल बायपास है। (//, /./, %2F, ; जैसे वेरिएंट काम नहीं करते क्योंकि path.Clean पहले दो को नॉर्मलाइज़ कर देता है और Puma %2F को अस्वीकार कर देता है।)
फिर बस query params के रूप में जाली अहस्ताक्षरित upload metadata भेजें:
POST /api/v4/projects/1/repository/%63ommits?file=&file.path=<ABSOLUTE_PATH>&file.size=1&Content-Type=application/x-www-form-urlencoded
file= खाली होना requires :file, WorkhorseFile वैलिडेशन को संतुष्ट करता है (खाली nil में बदल जाता है)। रीड प्री-ऑथ होती है। बाइट्स को वापस पाना मज़ेदार हिस्सा है: urlencoded ब्रांच पर हेल्पर Rack::Utils.parse_nested_query(File.read(path)) चलाता है और parser errors को 400 बॉडी में इंटरपोलेट करता है। कोई भी % जिसके बाद दो hex अंक न हों, InvalidParameterError: invalid %-encoding (<raw file bytes>) उठाता है और फ़ाइल की सामग्री error message के अंदर वापस आ जाती है। JSON ब्रांच (Oj) कुछ भी लीक नहीं करती, यही कारण है कि यहाँ urlencoded content type मायने रखता है।
फिक्स (master 0d9ce3e7, backports 1fe30154 / b43c8b26 / 0ff7b6b2) तीनों एंडपॉइंट पर authenticate! जोड़ता है साथ ही /authorize pre-step, path/size के लिए केवल middleware-निर्मित UploadedFile पर भरोसा करता है, और parser errors को echo करना बंद कर देता है। बग दिसंबर 2025 में पेश हुआ था, यही कारण है कि प्रभावित रेंज 18.7 से शुरू होती है।
./exploit.py --url http://localhost:8080 --file /etc/hostname
./exploit.py --url http://localhost:8080 --file /opt/gitlab/embedded/service/gitlab-rails/config/gitlab.yml
स्क्रिप्ट सभी सत्यापित बायपास रूपों (एन्कोडेड सेगमेंट, ट्रेलिंग स्लैश, .json; commits + files एंडपॉइंट, POST और PUT) से गुज़रती है और प्रत्येक प्रतिक्रिया को वर्गीकृत करती है ताकि आप बता सकें कि चेन में कहाँ कोई प्रोब विफल हुआ। ज़ाहिर है, केवल अधिकृत लक्ष्यों पर।
% हो जिसके बाद दो hex अक्षर न हों।) तक (.*)). स्टॉक JSON error के विरुद्ध ठीक है, अगर सामने कुछ प्रतिक्रिया को HTML में लपेट दे तो over-capture करेगा। उचित फिक्स JSON message फ़ील्ड को पार्स करना है।before ब्लॉक 404 देता है)।