
पृथक Docker लैब और गैर-विनाशकारी Python स्कैनर जो CVE-2026-94545 को पुनरुत्पादित करता है, Next.js next/og ImageResponse SVG इंजेक्शन, कमजोर बनाम पैच किए गए नियंत्रणों और पिक्सेल-स्तरीय PNG साक्ष्य के साथ।
next/og ImageResponse SVG इंजेक्शन (GHSA-vcvr-r3jv-pc5j)CVE-2026-94545 को पुन: उत्पन्न करने और सत्यापित करने के लिए सुरक्षा-शोध सामग्री,
Next.js ImageResponse SVG-सीरियलाइज़ेशन इंजेक्शन (अपस्ट्रीम: Satori
GHSA-wx4j-mvgx-mqwp), एक पृथक, केवल-लूपबैक लैब में।
>=16.2.0 <16.3.6 (केवल Node.js ImageResponse)26a52aff); 15.5.26 में
संबंधित हार्डनिंग शामिल है, 15.x RCE समस्या से प्रभावित नहीं हैकेवल उन सिस्टम पर उपयोग करें जिनके आप स्वामी हैं या जिनका परीक्षण करने के लिए आपको स्पष्ट रूप से अधिकृत किया गया है। स्कैनर गैर-विनाशकारी है: यह एक छवि रेंडर करता है और पिक्सेल गिनता है। लक्ष्य पर कुछ भी लिखा, संग्रहीत या निष्पादित नहीं होता है।
स्थिति: आपूर्ति की गई लैब में सिद्ध।
सलाहकारी के सटीक रूट पैटर्न के विरुद्ध (हमलावर मान को
<svg><title>{value}</title></svg> में):
next 16.3.5 (vulnerable): injected 500x150 red rect rendered into the PNG
(75,000 #FF0000 pixels measured in the response)
next 16.3.6 (patched) : value escaped, 0 red pixels
satori 0.25.0 (vuln) : payload present raw in the serialized SVG string
satori 0.33.5 (fixed) : payload escaped to </title>
यह इंजेक्शन प्रिमिटिव को एंड-टू-एंड साबित करता है (सीरियलाइज़र -> एम्बेडेड SVG -> रास्टराइज़्ड आउटपुट)। यह रिमोट कोड निष्पादन प्रदर्शित नहीं करता: सलाहकारी RCE को डाउनस्ट्रीम SVG पार्सर से जोड़ती है, जिसके संवेदनशील विवरण लेखन के समय सार्वजनिक नहीं हैं। कोई फ़ाइल पठन, SSRF, या कोड निष्पादन का दावा नहीं किया गया है।
Satori <svg> नोड्स को translateSVGNodeToSVGString()
(src/handler/preprocess.ts) के साथ सीरियलाइज़ करता है, जो आउटपुट XML में बिना
एस्केपिंग के इंटरपोलेट करता था:
String(node) के माध्यम से लौटाए गए - बिना एस्केप (अब escapeXMLText)।" बाहर निकल जाता है।style मान कच्चे रूप में इंटरपोलेट किए गए।& को एनकोड नहीं करता था, इसलिए आंतरिक XML
एंटिटीज़ बाहरी SVG पार्स से बच गईं।expand.ts आंतरिक स्टाइल प्रॉपर्टीज़ (प्रीफिक्स _) को सीरियलाइज़्ड
स्टाइल्स में पास करता था - अब अस्वीकार कर दिया गया है।फिक्स (satori@26a52aff)
सब कुछ एक हार्डनिंग buildXMLString() के माध्यम से रूट करता है जो मानों को एस्केप
करता है और XML नामों को मान्य करता है। यह लैब इस सब को व्यवहारिक रूप से सत्यापित
करती है; सटीक अनुरोध और पिक्सेल साक्ष्य poc/ में हैं।
आवश्यकताएँ: Docker with Compose v2, होस्ट पर Python 3.10+ (स्कैनर के लिए), पहले रन पर npm तक नेटवर्क पहुँच।
./lab verify
कमजोर ऐप बनाता है ([email protected] से वेंडर किया गया next/og), HTTP प्रतिक्रिया में
मार्कर रेक्ट के रेंडर होने की आवश्यकता करता है, फिर पैच किया गया ऐप
([email protected]) और इसकी अनुपस्थिति की आवश्यकता करता है।
कमजोर नियंत्रण:
[*] #FF0000 pixels: 75000 (threshold 10000)
[!!!] INJECTION CONFIRMED: the attacker value escaped <title> and
became a rendered SVG element in the generated image.
RESULT: VULNERABLE CONTROL CONFIRMED
पैच किया गया नियंत्रण:
[*] #FF0000 pixels: 0 (threshold 10000)
[=] no injection observed
RESULT: PATCHED CONTROL CONFIRMED
./lab start vulnerable # 16.3.5 app on http://127.0.0.1:9481/og
./lab test # assert injection renders
./lab start patched # 16.3.6 app
./lab test # assert absence
./lab status
./lab reset
स्कैनर को हाथ से चलाएँ (डिफ़ॉल्ट लूपबैक; अधिकृत बाहरी लक्ष्यों के लिए स्पष्ट ओवरराइड की आवश्यकता है):
python3 poc/scan.py http://127.0.0.1:9481/og
python3 poc/scan.py https://host.example/og --param value \
--allow-authorized-non-loopback [--insecure]
पेलोड एक संतुलित XML है जो 500x150 शुद्ध-लाल <rect> ले जाता है। कमजोर
एंडपॉइंट पर मान <title> से बाहर निकल जाता है और रेक्ट एम्बेडेड SVG दस्तावेज़ में
एक वास्तविक एलिमेंट बन जाता है; स्कैनर लौटाए गए PNG में सटीक #FF0000 पिक्सेल
गिनता है (75,000 अपेक्षित; थ्रेशोल्ड 10,000)। पैच किए गए एंडपॉइंट पर मान एस्केप्ड
टेक्स्ट है और कोई लाल रेंडर नहीं होता। प्रोब लक्ष्य पर कुछ नहीं बदलता: एक GET
अनुरोध, एक छवि रेंडर।
ध्यान दें कि कैरियर जानबूझकर एक <rect> है, टेक्स्ट नहीं: एम्बेडेड SVG के अंदर
टेक्स्ट में रेंडरर में कोई लोड करने योग्य फ़ॉन्ट नहीं होता और इंजेक्ट होने पर भी
अदृश्य रहेगा।
इस भेद्यता का प्रमाण एक रेंडर किए गए रास्टर की पिक्सेल सामग्री है, और nuclei
मैचर PNG को डिकोड नहीं कर सकते। एक आकार-अंतर टेम्पलेट बनाया गया और अस्वीकार कर
दिया गया: पैच किए गए ऐप पर एस्केप्ड पेलोड अभी भी रेंडर होता है (शाब्दिक निष्क्रिय
टेक्स्ट के रूप में), इसलिए पैच किए गए सिस्टम पर भी बेनाइन-बनाम-पेलोड आकार भिन्न
होते हैं, और कमजोर और पैच किए गए डेल्टा के बीच का अंतर ऐप-विशिष्ट संपीड़न शोर
है। पैच किए गए सिस्टम पर फायर करने वाला मैचर भेजना किसी भी न भेजने से बुरा है।
poc/scan.py का उपयोग करें; यह प्रतिक्रिया छवि का सटीक मूल्यांकन करता है।
| संस्करण | मूल्यांकन |
|---|---|
| Next.js 16.3.5 (vendored @vercel/og) | HTTP पर एंड-टू-एंड इंजेक्शन पुन: उत्पन्न |
| Next.js 16.3.6 (vendored @vercel/og) | पैच किया गया नकारात्मक नियंत्रण पुन: उत्पन्न |
| satori 0.25.0 (npm) | सीरियलाइज़्ड SVG में कच्चा इंजेक्शन (स्रोत स्तर) |
| satori 0.33.5 (npm, 22 Sep 2026) | एस्केप्ड (स्रोत स्तर) |
| Next.js 16.2.0-16.3.4 | सलाहकारी के अनुसार प्रभावित; व्यक्तिगत रूप से परीक्षण नहीं किया गया |
| Next.js 15.x | विक्रेता ब्लॉग के अनुसार RCE समस्या से प्रभावित नहीं |
.
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── app/
│ └── server.mjs # advisory route pattern over loopback HTTP
└── poc/
└── scan.py # non-destructive scanner (Python stdlib: urllib + zlib)
कमिट नहीं किया गया: assets/ (npm-packed next tarballs + extracted bundles),
node_modules/, .lab-state.
| दावा | स्थिति |
|---|---|
| satori < fix में SVG-सीरियलाइज़ेशन इंजेक्शन | सिद्ध (स्रोत + व्यवहार) |
| next/og 16.3.5 के माध्यम से इंजेक्ट किए गए मार्कअप का एंड-टू-एंड रेंडरिंग | सिद्ध |
| पैच किया गया नकारात्मक नियंत्रण (16.3.6 / satori 0.33.5) | सिद्ध |
| एट्रिब्यूट/स्टाइल कैरियर (समान मूल कारण) | फिक्स डिफ में मूल कारण की पुष्टि; टेक्स्ट कैरियर सिद्ध |
| इंजेक्ट किए गए संसाधन संदर्भों के माध्यम से SSRF / फ़ाइल पठन | परीक्षण नहीं किया गया |
| रिमोट कोड निष्पादन | दावा नहीं किया गया (अप्रकट डाउनस्ट्रीम पार्सर विवरण पर निर्भर) |
इस रिपॉज़िटरी को केवल उन सिस्टम पर चलाएँ जिनके आप स्वामी हैं या जिनका परीक्षण करने
के लिए आपको स्पष्ट रूप से अधिकृत किया गया है। लैब ड्राइवर vulnerable/patched
के अलावा अन्य वेरिएंट से इनकार करता है, स्कैनर गैर-लूपबैक लक्ष्यों से इनकार करता
है जब तक कि --allow-authorized-non-loopback प्रदान न किया जाए, और Compose पोर्ट
127.0.0.1 से बाइंड है।