Skip to content

VNX-CS-007: C# path traversal: request data reaches Path.Combine, MapPath or a file-system API

Detects C# Path.Combine/Path.Join, MapPath and file-system APIs (File, Directory, FileStream, StreamReader, PhysicalFile, TransmitFile, SaveAs) whose path argument contains HTTP request data, an uploaded file name, an archive entry name (zip slip) or a controller action parameter, without Path.GetFileName or a GetFullPath/StartsWith containment check.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-CS-007
Severity High
SARIF level error
Kind sast
Languages C#
CWE CWE-22
CAPEC CAPEC-126
ATT&CK T1083
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags path-traversal csharp
Source internal/sast/rules/vnx-cs-007.rego

Overview

This rule reports Path.Combine/Path.Join and MapPath (any argument), File.Copy/Move/Replace, and the path argument of File.Read*/Write*/Append*/Open*/Create/Delete, Directory enumeration and deletion, new FileStream/StreamReader/StreamWriter/FileInfo/DirectoryInfo, PhysicalFile, Results.File and Response.TransmitFile/WriteFile/SaveAs, when that argument contains request data: Request.Query/QueryString/Form/Params/Cookies/Headers/Path, Request[...], RouteData.Values, a WebForms TextBox.Text, an uploaded file’s FileName, a string parameter of a controller action, a [FromQuery]/[FromRoute]/[FromForm] parameter or a minimal-API lambda parameter, inline or through a variable assigned up to 15 lines above. A flow is reported once, at its first sink. Values passed through Path.GetFileName, a path resolved with GetFullPath and checked with StartsWith, values checked by Regex.IsMatch or an allowlist, Guid/int parameters, request data written as file content, and parameters of non-controller methods are not reported.

Path.Combine in .NET concatenates path segments but does not validate the result. If a user-supplied segment contains ../ sequences (or their URL-encoded equivalents %2e%2e%2f), the resolved path escapes the intended base directory. An attacker can use this to read arbitrary files readable by the web application process, write files to unintended locations, or overwrite critical system files if the process has sufficient privilege.

The vulnerability is particularly common in file upload handlers, file download endpoints, and any feature that constructs a filesystem path from a user-supplied filename or path component.

Why This Matters

Path traversal vulnerabilities have affected a wide range of applications including web servers, file managers, and document management systems. The OWASP Top 10 has consistently included path traversal under “Broken Access Control”. Exploits are simple: an attacker submits a filename like ../../appsettings.json or ../../../windows/win.ini and the server returns the file contents, potentially exposing database connection strings, API keys, and other configuration secrets.

In ASP.NET applications, path traversal in file download endpoints is a common finding in penetration tests. A vulnerable endpoint that constructs a path as Path.Combine(baseDir, Request.Query["file"]) allows reading any file accessible to the IIS/Kestrel worker process, including web.config files with database credentials and the appsettings.json configuration. In write scenarios, attackers may overwrite the application’s own DLLs or web configuration, leading to code execution.

The attack is covered by CAPEC-126 (Path Traversal) and ATT&CK T1083 (File and Directory Discovery). Modern WAFs may detect some traversal patterns, but canonicalisation-based bypasses (double-encoding, Unicode normalisation tricks) mean that WAF-only defence is insufficient.

What Gets Flagged

// FLAGGED: Path.Combine with query string value, no validation
string filename = Request.Query["file"];
string fullPath = Path.Combine(uploadDir, filename);
return File(System.IO.File.ReadAllBytes(fullPath), "application/octet-stream");

// FLAGGED: controller action parameter
public IActionResult Download(string name)
{
    var path = Path.Combine(_root, name);
    return PhysicalFile(path, "application/pdf");
}

// FLAGGED: WebForms MapPath and an uploaded file's own name
FileInfo fi = new FileInfo(Server.MapPath(Request["image"]));
var dest = Path.Combine(root, "uploads", file.FileName);

// FLAGGED: zip slip, an archive entry name keeps its ../ segments
entry.ExtractToFile(Path.Combine(Root, entry.FullName), overwrite: true);

Remediation

  1. Use Path.GetFileName(userInput) to strip any directory components from the user-supplied value before passing it to Path.Combine. This ensures only the bare filename is used.
  2. After combining, resolve the full canonical path with Path.GetFullPath() and verify it starts with your intended base directory using string.StartsWith(baseDir, StringComparison.OrdinalIgnoreCase).
  3. Maintain an allowlist of permitted file names or use GUIDs as file identifiers instead of accepting user-supplied names.
  4. Apply least-privilege to the process account so even a successful traversal cannot reach sensitive files outside the application’s directory.
// SAFE: strip directory components from user input first
string userFilename = Request.Query["file"];
string safeFilename = Path.GetFileName(userFilename);  // removes any ../ components
string fullPath = Path.Combine(uploadDir, safeFilename);

// SAFE: canonicalise and verify the resulting path is within the base directory
string baseDir = Path.GetFullPath("/var/app/uploads");
string requested = Path.Combine(baseDir, userFilename);
string canonical = Path.GetFullPath(requested);

if (!canonical.StartsWith(baseDir + Path.DirectorySeparatorChar, 
    StringComparison.OrdinalIgnoreCase))
{
    return Forbid();  // path traversal detected
}
return File(System.IO.File.ReadAllBytes(canonical), "application/octet-stream");

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-cs-007
// nosec vnx-cs-007

Or record an ignore rule: vulnetix ignore add --rule vnx-cs-007 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-cs-007.rego, cli/internal/sast/docs/vnx-cs-007.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.