Relative Path Traversal

Description

Relative Path Traversal is a vulnerability that occurs when software uses external input to construct a pathname intended to identify a file or directory within a restricted parent directory, but fails to properly neutralize sequences such as ".." that can resolve to a location outside of the restricted directory. Unlike absolute path traversal which uses complete filesystem paths, relative path traversal exploits the ability to navigate the directory hierarchy using relative references. Attackers manipulate user-supplied input by inserting "../" (dot-dot-slash) sequences to traverse upward through the directory structure, escaping the intended directory boundary to access sensitive files elsewhere on the system.

Risk

Relative path traversal vulnerabilities allow attackers to break out of an application's intended directory scope by exploiting the parent directory reference ".." in file paths. This can lead to unauthorized reading of configuration files containing credentials, database connection strings, encryption keys, and other sensitive data. System files like /etc/passwd on Unix or boot.ini on Windows become accessible, potentially revealing user accounts and system information. In severe cases where write operations are possible, attackers may upload webshells, modify configuration files, or overwrite critical system components. The widespread nature of file operations in web applications makes this vulnerability class extremely common and dangerous.

Solution

Implement server-side path validation that resolves all relative references before checking if the resulting path remains within allowed boundaries. Use canonical path functions like realpath() in PHP, os.path.realpath() in Python, or Path.GetFullPath() in C# to convert relative paths to absolute paths, then verify the resolved path starts with the expected base directory. Employ an allowlist approach limiting accessible files to specific names or patterns rather than trying to block malicious sequences. Remove or encode dangerous characters and sequences from user input, including "../", "..\", and URL-encoded variants. Consider using indirect file references where users specify an identifier that maps to actual file paths server-side.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Attackers can traverse directories to read sensitive files including configuration files, source code, credentials, and private data stored outside the intended directory.
IntegrityScope: Integrity

If the vulnerability allows file writing with relative paths, attackers can overwrite configuration files, inject malicious code, or modify critical application data.
AvailabilityScope: Availability

Attackers could potentially delete or corrupt critical files by traversing to system directories, causing application failures or system instability.
Access ControlScope: Access Control

Accessing authentication files or session data through path traversal can enable privilege escalation and unauthorized access to protected functionality.

Example Code + Solution Code

The following example demonstrates a vulnerable Node.js application that serves static files using relative path input:

Vulnerable Code

const express = require('express');
const path = require('path');
const fs = require('fs');

const app = express();
const UPLOADS_DIR = './uploads';

app.get('/download', (req, res) => {
    // VULNERABLE: Directly using user input in file path
    const filename = req.query.file;
    const filepath = path.join(UPLOADS_DIR, filename);

    // Attacker can use: ?file=../../../etc/passwd
    if (fs.existsSync(filepath)) {
        res.download(filepath);
    } else {
        res.status(404).send('File not found');
    }
});

app.listen(3000);

The vulnerability exists because path.join() does not prevent directory traversal - it simply concatenates paths. An attacker can supply ../../../etc/passwd to escape the uploads directory and access arbitrary files.

Fixed Code

const express = require('express');
const path = require('path');
const fs = require('fs');

const app = express();
const UPLOADS_DIR = path.resolve('./uploads');

// Allowlist of permitted file extensions
const ALLOWED_EXTENSIONS = ['.pdf', '.jpg', '.png', '.txt'];

app.get('/download', (req, res) => {
    const filename = req.query.file;

    // Validate filename is provided and non-empty
    if (!filename || typeof filename !== 'string') {
        return res.status(400).send('Invalid filename');
    }

    // Check for obvious traversal attempts (defense in depth)
    if (filename.includes('..') || filename.includes('\0')) {
        console.warn(`Path traversal attempt detected: ${filename}`);
        return res.status(403).send('Access denied');
    }

    // Resolve to absolute path
    const requestedPath = path.resolve(UPLOADS_DIR, filename);

    // Critical: Verify the resolved path is within the uploads directory
    if (!requestedPath.startsWith(UPLOADS_DIR + path.sep)) {
        console.warn(`Path traversal blocked: ${filename} -> ${requestedPath}`);
        return res.status(403).send('Access denied');
    }

    // Validate file extension
    const ext = path.extname(requestedPath).toLowerCase();
    if (!ALLOWED_EXTENSIONS.includes(ext)) {
        return res.status(403).send('File type not allowed');
    }

    // Check file exists and is a regular file (not a directory or symlink)
    try {
        const stats = fs.lstatSync(requestedPath);
        if (!stats.isFile()) {
            return res.status(404).send('File not found');
        }
    } catch (err) {
        return res.status(404).send('File not found');
    }

    res.download(requestedPath);
});

app.listen(3000);

The fixed code uses path.resolve() to obtain the canonical absolute path and verifies it starts with the uploads directory path (including the path separator to prevent partial matches). Additional defenses include checking for ".." sequences, null bytes, validating file extensions against an allowlist, and verifying the target is a regular file.


Exploited in the Wild

Fortinet FortiOS SSL VPN Credential Theft (Multiple Organizations, 2019-2021)

CVE-2018-13379 in Fortinet FortiOS allowed attackers to use relative path traversal sequences to read the sslvpn_websessions file containing plaintext VPN credentials. The vulnerability was exploited extensively, with credentials for approximately 50,000 vulnerable devices leaked online in November 2020. APT actors used this vulnerability to gain initial access to government agencies, critical infrastructure, and private sector organizations worldwide.

Pulse Secure VPN Campaign (Global, 2019-2021)

CVE-2019-11510 exploited a relative path traversal vulnerability in Pulse Secure VPN appliances, allowing unauthenticated attackers to read arbitrary files by traversing with "../" sequences past hardcoded directory allowlists. Travelex, the currency exchange company, suffered a major ransomware attack after criminals exploited this flaw. CISA reported compromises affecting multiple U.S. government agencies and critical infrastructure entities, with stolen Active Directory credentials used for persistent access months after patches were applied.

Kubernetes kubectl Copy Vulnerability (Cloud Environments, 2019)

A relative path traversal vulnerability was discovered in kubectl, the command-line interface for Kubernetes clusters. The flaw in the cp command allowed malicious container images to overwrite files on a user's workstation when copying files from a pod. Since kubectl is used by administrators managing cloud infrastructure, exploitation could lead to code execution on privileged systems or compromise of entire cluster management environments.


Tools to test/exploit

  • Burp Suite — web security testing platform with Intruder payloads for relative path traversal including various encoding schemes like URL encoding, double encoding, and Unicode normalization.

  • dotdotpwn — dedicated directory traversal fuzzer that automatically generates and tests numerous "../" payload variations across multiple protocols and encoding formats.

  • Wfuzz — web fuzzer that can be used with path traversal wordlists to discover vulnerable parameters and test various traversal sequences systematically.


CVE Examples

  • CVE-2018-13379 — Fortinet FortiOS SSL VPN relative path traversal allowing unauthenticated file read via "../" sequences.

  • CVE-2019-11510 — Pulse Secure Connect relative path traversal bypassing path restrictions through encoded traversal sequences.

  • CVE-2023-39139 — Archive npm package relative path traversal via maliciously crafted archive entries enabling arbitrary file write.

  • CVE-2021-41773 — Apache HTTP Server path traversal through encoded "../" sequences allowing access outside document root.


References

  1. MITRE. "CWE-23: Relative Path Traversal." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/23.html

  2. OWASP. "Path Traversal." OWASP Foundation. https://owasp.org/www-community/attacks/Path_Traversal

  3. Snyk. "What is directory traversal?" Snyk Learn. https://learn.snyk.io/lesson/directory-traversal/

  4. Invicti. "Directory Traversal (Path Traversal)." https://www.invicti.com/learn/directory-traversal-path-traversal