Safeguard
Application Security•

CWE-24 Path Traversal with '../filedir': The Canonical Escape

The plain dot-dot-slash is the classic path traversal. A walkthrough of CWE-24, why it still works, how to test for it, and how to fix it in several languages.

Safeguard Security Team
5 min read

If path traversal has a signature, it is "../filedir". CWE-24 is the entry for exactly that: the product builds a pathname from external input, expects it to stay inside a restricted directory, and fails to neutralize "../" sequences that resolve to a location outside it.

MITRE describes "../" as the canonical manipulation for operating systems that use "/" as a directory separator, such as UNIX and Linux based systems. It adds that it is also useful where "/" is supported but is not the primary separator, such as Windows, which uses the backslash but accepts the slash too.

That second point is easy to miss. Plenty of Windows services are exposed to "../" because the platform tolerates it.

How it happens

Walk through a request. A site stores invoices in "/srv/app/invoices" and offers a link: "/invoice?file=march.pdf". The handler builds "/srv/app/invoices/march.pdf" and returns it.

Now the attacker requests "/invoice?file=../../../etc/passwd". The handler builds "/srv/app/invoices/../../../etc/passwd". The operating system collapses the dots, one parent per "../", and arrives at "/etc/passwd". Three climbs took the path from the invoices folder, to app, to srv, to the root, and then down into etc.

The application did exactly what it was written to do. It joined a base to a name. The missing piece was a question: does the result still sit under the base?

This is the same bug in different languages.

PHP:

$file = $_GET['file'];
readfile("/srv/app/invoices/" . $file);

Java:

Path p = Paths.get("/srv/app/invoices", request.getParameter("file"));
return Files.readAllBytes(p);

Node:

const target = path.join("/srv/app/invoices", req.query.file);
fs.createReadStream(target).pipe(res);

In each, the user controlled name flows into a file API with no containment check.

What an attacker gains

The record lists confidentiality and integrity: reading files or directories and modifying files or directories.

Reading is the common outcome. Typical targets are configuration files with database passwords, application source, private keys and system files that reveal accounts. Writing, where an endpoint accepts uploads or saves, can plant files in places the application later loads or executes.

MITRE gives one observed case, CVE-2022-45918. It is a chain in a learning management tool whose debugger used external input to locate previous session logs and did not properly validate the given path, allowing file system traversal. Note that the vulnerable feature was a debugger, not the main product surface. Auxiliary and developer features get less review, and they open the same door.

How to find it

The record names automated static analysis (SAST), which analyzes source or compiled code without running it, typically by modeling data flow from input to a dangerous call. Path traversal fits that model well.

Beyond tools:

  1. Grep for file API calls and read each argument backward to its source.
  2. Pay attention to secondary features: debug views, log viewers, export functions, admin utilities.
  3. Test with a ladder of depths. Try one, two, three, four and more "../" prefixes, because the right depth depends on where the base sits.
  4. Test the Windows forms as well, and mixed separators.
  5. Confirm the response. A real hit returns file content. A refusal should be an explicit denial, not a lucky "not found".

Try it yourself

Create a small folder with two files, one inside a "public" subfolder and one outside. Write the vulnerable handler for your language of choice, then request the outside file with "../". Seeing it work on your own machine makes the fix obvious. Then apply the fix below and confirm the same request now fails.

How to fix it

The record lists no mitigations, so these are the standard defenses.

  1. Do not let users name paths. Map an ID to a file name on the server.
  2. If you must accept a name, resolve it to its absolute, canonical form and check that it is inside the base, comparing whole path components.
  3. Handle links. A symbolic link inside the folder that points outside defeats a text comparison. Resolve links before the comparison.
  4. Reject, do not repair. Stripping "../" from input invites bypasses, such as nested sequences that rebuild the original after the removal.
  5. Restrict the process. A service that can read only its own content folder turns a bypass into a smaller incident.
  6. Log denied attempts. A burst of requests containing dots is an early warning.

Fixed PHP:

$base = realpath('/srv/app/invoices');
$full = realpath($base . '/' . $_GET['file']);
if ($full === false || strpos($full, $base . DIRECTORY_SEPARATOR) !== 0) {
    http_response_code(403);
    exit;
}
readfile($full);

The separator in the prefix test blocks a sibling folder named "invoices_old" from passing as a match.

Fixed Node:

const base = fs.realpathSync("/srv/app/invoices");
const full = fs.realpathSync(path.join(base, req.query.file));
if (!full.startsWith(base + path.sep)) {
  return res.status(403).end();
}
fs.createReadStream(full).pipe(res);

If the file does not exist, realpathSync throws. Catch it and return a not found response, so a missing file and a blocked file look the same to the caller.

Libraries are part of the picture, too. A path handling flaw in a dependency reaches you without any of your code being wrong. A dependency scan lists the known issues in your project, and reachability analysis shows whether your code actually calls the vulnerable function. The free in-browser scan at gold.safeguard.sh/scan requires no login, the file stays on your machine, and it handles up to 100 dependencies.

Quick check

How many "../" do I need to reach the root?

It depends on how deep the base folder is. Attackers try many depths, so tests should do the same.

Why does a missing file matter in the fix?

Canonical resolution can fail for files that do not exist. Handle that case, and return the same response for missing and blocked files.

Does this affect Windows servers?

Yes. MITRE notes Windows accepts the forward slash as well as the backslash, so "../" can work there.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.