Back to scan results
Check 33 of 45

Path Traversal

We test public file-like GET inputs with bounded POSIX, Windows, filter-bypass, and double-decoding path forms. The only file used as a read canary is the target's already-public /robots.txt.

What this check probes

The discovery stage inspects same-origin GET forms and links for parameters such as file, path, page, document, download, filename, template, and include. A small root-page fallback covers common names when no suitable public form is advertised.

PCIScan first fetches the target's public /robots.txt and validates that it is a usable text canary. Each selected input receives single- and two-level traversal forms using forward slashes, backslashes, repeated separators, and a double-decoding representation.

The check fails only when the validated robots.txt content appears in a traversal response and was absent from the same input's missing-file baseline. New framework-specific path exceptions or new HTTP 5xx responses produce a warning. Explicit traversal-rejection messages and blocking status codes count as protection.

If the target has no usable public robots.txt, error-based testing still runs but the result warns that content-based confirmation was unavailable. The scanner never requests password files, registry data, configuration, source code, credentials, environment files, logs, process data, or other sensitive filesystem content.

Why this matters for PCI DSS

Path traversal can let an attacker escape an intended download, template, or document directory and read application secrets, payment data, keys, or operating-system files. Write-capable variants can be even more damaging.

PCI DSS secure-development and access-control requirements expect applications to constrain untrusted paths, enforce authorization, protect sensitive files, and avoid excessive service-account privileges. A pass here covers only the public GET inputs found by this bounded scan.

How to fix it

Do not accept filesystem paths from the client. Accept an opaque file identifier, authorize it for the current user, and map it to a fixed server-side filename. This pattern is compatible with .NET Framework 4.7.2 and 4.8:

var allowedFiles = new Dictionary<string, string>(StringComparer.Ordinal)
{
    { "invoice-current", "invoice-2026-08.pdf" }
};

string fileName;
if (!allowedFiles.TryGetValue(fileId, out fileName))
    throw new HttpException(404, "File not found.");

string baseDir = Path.GetFullPath(Server.MapPath("~/App_Data/Downloads"))
               + Path.DirectorySeparatorChar;
string fullPath = Path.GetFullPath(Path.Combine(baseDir, fileName));

if (!fullPath.StartsWith(baseDir, StringComparison.OrdinalIgnoreCase))
    throw new HttpException(400, "Invalid file path.");

Perform authorization after resolving the identifier and before opening the file. Keep downloadable files outside the public web root, deny directory browsing, and run the application account with read access only to the specific storage directory it needs.

Canonical-path checks are defense in depth, not a substitute for indirect identifiers. Account for symbolic links or Windows reparse points, alternate data streams, mixed separators, Unicode normalization, repeated decoding, case rules, and archive extraction paths.

Apply the same containment rule to uploads, ZIP extraction, templates, image processors, report generators, imports, and file-deletion features. Return a generic error rather than exposing the resolved path, stack trace, storage layout, or service-account name.

Fixed it? Re-run the scan to confirm.

Run scan again