
تحليل مفصل واستغلال إثبات المفهوم لـ CVE-2017-9822، وهي ثغرة XXE/إلغاء تسلسل غير آمن في DotNetNuke CMS تؤدي إلى تنفيذ التعليمات البرمجية عن بُعد عبر التلاعب بملفات تعريف الارتباط.
DotNetNuke (يُختصر عادةً باسم DNN) هو نظام إدارة محتوى (Content Management System) وإطار عمل لتطبيقات الويب (web application framework) مبني على تقنية ASP.NET من مايكروسوفت.
المنتج المتأثر: DotNetNuke (منصة DNN) – نظام CMS/بوابة .NET شائع.
تاريخ الإعلان: يوليو 2017.
مستوى الخطورة: حرج (CVSS ~9.8).
نوع الثغرة: XML External Entity (XXE) / إلغاء تسلسل غير آمن (Insecure Deserialization) → تنفيذ التعليمات البرمجية عن بُعد (Remote Code Execution - RCE).
التأثير: الإصدارات قبل 9.1.1 قابلة لتنفيذ التعليمات البرمجية عن بُعد عبر الكوكيز
هنا أستخدم نظام Windows 10 لإعداد البرنامج وتصحيح أخطائه (debug). الإصدار الذي أقوم بتثبيته هو 9.1.0، ويمكنكم الاطلاع على طريقة التثبيت هنا. والنتيجة بعد الانتهاء هي:


وفقًا للتقارير التي قرأتها، تقع هذه الثغرة في مكان معالجة الكوكيز في DotNetNuke
يستخدم DNN أسلوب إلغاء تسلسل غير آمن (unsafe deserialization) لكوكيز DNNPersonalization

.dll أو .exe المكتوبة بلغة .NET. يمكن تثبيته هنا. نحتاج إلى تحميل نسختين لأغراض التصحيح (debug).
DotNetNuke.dll باستخدام الإصدار 32 بت واختر Edit Assembly Attributes (C#)
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default |
DebuggableAttribute.DebuggingModes.DisableOptimizations |
DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints |
DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]

ثم احفظ التغييرات.
Attach to Process
w3wp.exe
سبب اختيار w3wp.exe هو:
w3wp.exe = IIS Worker Process.
إنها عملية التنفيذ الخاصة بـ Application Pool في IIS.
عند وصول طلب HTTP إلى الموقع، يقوم IIS بإنشاء أو إعادة استخدام w3wp.exe لمعالجة ذلك الطلب (تشغيل كود ASP.NET، معالجة الوحدات (modules)، الوسائط (middleware)، اتصالات قاعدة البيانات...).
يمكن أن يحتوي كل Application Pool على عملية w3wp.exe واحدة أو أكثر حسب الإعدادات (web garden, recycling).
بعد ذلك، دعنا نختار Debug -> Window -> Modules

بعد الانتهاء ستظهر الوحدات (Modules)، اضغط بزر الفأرة الأيمن على أيٍّ منها واختر Open All Modules

وأخيرًا ستظهر جميع التجميعات (Assemblies) المتعلقة بـ DNN

DotNetNuke.dll -> PersonalizationController#LoadProfile(int, int)
تُستخدم هذه الدالة لتحميل بيانات التخصيص (profile) الخاصة بالمستخدم في بوابة DNN.
إذا كان المستخدم مسجلاً الدخول → يتم جلب الملف الشخصي من قاعدة البيانات + الذاكرة المؤقتة (cache).
إذا كان المستخدم مجهولاً (لم يسجل الدخول) → يتم جلب الملف الشخصي من كوكيز DNNPersonalization.
هنا يجب أن نركز على DNNPersonalization
إذا كانت userId غير صالحة (مستخدم مجهول).
التحقق مما إذا كان الطلب يحتوي على كوكيز DNNPersonalization.
إذا كانت موجودة → يتم أخذ قيمة XML من هذه الكوكيز.
سنرسل طلب 404 إلى الموقع ونستخدم أي DNNPersonalization، وباستخدام dnSpy لتعيين نقطة توقف (Breakpoint) عند DotNetNuke.dll –> PersonalizationController#LoadProfile(int, int) سنتمكن من التصحيح (debug)


PortalSettings
الجدير بالملاحظة هو استخدام شرط if هنا للتحقق مما إذا كان الطلب الحالي IsAuthenticated أم لا
وبما أن الطلب الذي أرسلناه هو 404 -> unauthenticated
نكمل في قسم Call Stack ونركز على Handle404OrException

context.User في الطلب الحالي هو null، وإذا كان الأمر كذلك فسيتم تعيين context.User كمستخدم الخيط (thread) الحالي

نلاحظ أنه داخل Handle404OrException أصبح المتغير IsAuthenticated الآن بقيمة true، وأن المستخدم هو مستخدم خادم IIS، وبالتالي يتم تنفيذ الطلب كمستخدم موثّق (authenticated user).
سبب المشكلة يكمن في هذا الجزء من الكود
else if (transfer)
{
if (context.User == null)
{
context.User = Thread.CurrentPrincipal;
}
response.TrySkipIisCustomErrors = true;
IHttpHandler handler = new CDefault();
context.Handler = handler;
server.Transfer("~/" + text, true);
}
إذا لم يكن context.User موجودًا → يتم تعيين Thread.CurrentPrincipal (أي هوية الخيط الحالية).
يساعد هذا الطلب في الحصول على معلومات المستخدم/الدور (role) عند المعالجة اللاحقة.
=> عندما نمرر أي محتوى إلى الكوكيز مع متغير DNNPersonalization، فسيتم تنفيذه كمستخدم عادي.
ما زلنا داخل DotNetNuke.dll –> PersonalizationController#LoadProfile(int, int)
نرى أن المتغير text يأخذ القيمة من الكوكيز ثم يُمرر كمدخل إلى Globals.DeserializeHashTableXml()

Globals.DeserializeHashTableXml()
تقوم دالة DeserializeHashTableXml بما يلي: