Safeguard
Application Security•

CWE-34 Path Traversal with '....//' and Sequential Filter Collapse

Filter out dot-dot-slash in one pass and '....//' turns back into it. How CWE-34 works, why sequential removal is unsafe, and how to check the resolved path instead.

Safeguard Security Team
6 min read

This is one of the neatest bypasses in the path traversal family, and it is neat because the defender does the attacker's work. CWE-34 covers input shaped like "....//" (doubled dot dot slash). The product builds a path from external input and does not neutralize it, so the path can resolve to a location outside the restricted directory.

MITRE explains the trick. If "../" is filtered in a sequential fashion, as some regular expression engines do, then "....//" can collapse into the "../" unsafe value. The record also notes a second route: if ".." is removed and the operating system treats "//" and "/" as equivalent, the result is still a working slash.

The collapse into an unsafe value is tracked separately as CWE-182. CWE-34 is what it looks like in a file path.

How it happens

Follow the string through a filter that deletes every "../" it finds, scanning left to right in a single pass.

The input is "....//". Inside it, the filter finds "../" in the middle: the third and fourth characters are dots, followed by the first slash. It removes those three characters. What remains is the first two dots from the front and the second slash from the end, joined together: "../".

The filter ran, removed what it was told to remove, and handed back a working parent directory reference. Nothing in the filter is buggy in the narrow sense. Its design is wrong, because it assumes that deleting a bad substring leaves a good string.

In Python:

def clean(name):
    return name.replace("../", "")   # one pass

full = os.path.join("/srv/app/files", clean(user_input))

With user_input set to "....//....//etc/passwd", clean returns "../../etc/passwd" and the join resolves to a file outside the folder.

PHP has the same pattern, and it shows up often in older code:

$name = str_replace('../', '', $_GET['file']);
readfile('/srv/app/files/' . $name);

The second route in MITRE's note is less common but worth knowing. If the code removes ".." instead of "../", the input "..//" leaves "//" behind, which many operating systems treat the same as "/". A cleaned path that starts with a double slash may then be read as an absolute path, which is also outside the base.

What an attacker gains

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

The one observed case MITRE gives is CVE-2004-1670. A mail server let remote attackers create arbitrary directories using ".." in user supplied parameters, or rename arbitrary files using "....//". That is worth reading closely. Directory creation and renaming are write operations. This weakness is not limited to reading secrets. It can change the state of the file system.

How to find it

The record lists two detection approaches that matter here.

Automated static analysis of source code can give partial coverage. A source code weakness analyzer, configured for your context, can flag file operations that take external input.

Architecture or design review is rated by MITRE, through the SOAR report, as highly cost effective. Inspection of requirements, design and source code is the method. It fits this weakness because the flaw is in the design of a filter. A reviewer can look at a replace call and see the problem in minutes, if they know to check what remains after the removal.

Practical steps:

  1. Search for replace, strip, sub and regular expression calls on values used as paths.
  2. For each, work an example by hand. Write down what is left after the removal, and ask whether that leftover could be dangerous.
  3. Test with nested sequences: "....//", "..../" and longer variants such as ".../...//". Also try them repeated.

If a request with the nested form succeeds where the plain form is refused, the filter collapses.

How to fix it

The record has no mitigation list. These steps follow from how the bypass works.

  1. Delete the filter. Sequential removal is the root problem, and adding another pass or a longer pattern only moves the bypass.
  2. Resolve the final path with the platform resolver, then check that it sits inside the permitted base. The nested sequence no longer matters, because the check looks at the destination.
  3. If you keep any removal step, loop until the string no longer changes, and still verify the resolved path afterward. Rejection is cleaner than repair.
  4. Compare path components, not text prefixes.
  5. Where users pick a file, use an ID mapped to a name on the server.
  6. Limit the service account to the content folder, with no write access outside it.

Fixed Python:

import os

BASE = os.path.realpath("/srv/app/files")

def open_file(user_input):
    full = os.path.realpath(os.path.join(BASE, user_input))
    if os.path.commonpath([BASE, full]) != BASE:
        raise PermissionError("outside base directory")
    return open(full, "rb")

Fixed PHP:

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

Neither version needs to know that "....//" is special. It resolves to a location outside the base and gets refused like any other escape.

The same thinking applies to dependencies. A library that cleans paths with sequential removal carries this flaw into every project that uses it. A dependency scan lists known issues, and reachability analysis shows whether your code calls the affected function. The free in-browser scan at gold.safeguard.sh/scan needs no login, the file stays on your machine, and it covers up to 100 dependencies.

Quick check

What does "....//" become after one pass of removing "../"?

The filter removes the "../" in the middle. The remaining pieces join into "../", which is the dangerous sequence the filter was meant to eliminate.

Why is looping the filter still not enough?

Looping fixes the collapse, but you remain exposed to forms the filter does not model. Checking the resolved path covers all of them.

Is this only a file read problem?

No. The observed mail server case involved creating directories and renaming files, which are writes.

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.