Directory Traversal Attack
A directory traversal attack exploits insufficient validation of user-supplied file paths to break out of the intended directory and access arbitrary locations on the server's file system. By injecting relative path sequences such as ../ (dot-dot-slash), encoded variants, or absolute paths, the attacker reads confidential files, discovers the application's internal structure, and β when combined with write primitives β achieves remote code execution.
How Directory Traversal Attack Works
Every web application confines its file operations to a designated root directory β the document root for static assets, an upload folder for user content, or a template directory for views. Directory traversal breaks this confinement. When the application builds a file system path using unsanitized user input, the attacker injects sequences that climb the directory tree until reaching the system root, then descends into any target directory. The attack surface is enormous: any parameter, header, cookie, or multipart field that ultimately feeds into a file system API is a potential entry point.
Map the attack surface
The attacker identifies every input that influences file system operations: URL path segments (/static/../../etc/passwd), query parameters (?file=report.pdf), POST body fields, multipart filenames, Cookie values, and HTTP headers (Content-Disposition, X-Forwarded-File). Automated scanners like DirBuster, ffuf, and Burp Intruder systematically probe these entry points. Error messages are invaluable β a 'file not found' exception revealing /var/www/app/pages/../../test confirms that the input reaches the file system and the traversal is interpreted.
Determine the traversal depth
The attacker calculates how many ../ sequences are needed to reach the file system root from the application's base directory. A common technique is to request increasingly deep traversal sequences (../, ../../, ../../../, etc.) against a file known to exist at the root β /etc/passwd on Linux or C:\Windows\win.ini on Windows. Once the file is returned, the depth is established. Over-traversing (using more ../ than needed) is harmless: the OS simply stays at the root.
Bypass input filters and WAF rules
Modern applications and WAFs detect basic ../ sequences, so attackers employ an arsenal of evasion techniques: URL encoding (%2e%2e%2f), double URL encoding (%252e%252e%252f), UTF-8 overlong encoding (..%c0%af), mixed OS separators (..\../ on Windows IIS), path normalization tricks (....// where stripping ../ once leaves ../), null byte injection (%00 to truncate appended extensions on legacy runtimes), and Unicode normalization exploits (..%u2216 using fullwidth characters). Each technique targets a different layer in the parsing stack β the web server, the framework, or the language runtime.
Extract high-value targets
With a working traversal payload, the attacker systematically extracts files in a prioritized order: (1) /etc/passwd to enumerate system users; (2) application configuration files (.env, config.php, application.yml, appsettings.json) containing database credentials, API keys, and encryption secrets; (3) /etc/shadow (if running as root) for password hashes; (4) SSH private keys (~/.ssh/id_rsa); (5) application source code to audit for further vulnerabilities; (6) cloud metadata (AWS /proc/self/environ, /var/run/secrets/kubernetes.io for K8s tokens); (7) log files containing session tokens and user data.
Escalate to code execution
When the traversal extends to write operations (log injection, file upload with path manipulation, or template overwriting), the attacker achieves code execution: overwriting a server-side template with malicious code, writing a PHP web shell into the document root, injecting executable content into a cron job file, or modifying .bashrc/.profile for the web server user. Even read-only traversal can lead to full compromise when extracted credentials (database passwords, API tokens, SSH keys) provide lateral movement pathways into other systems.
Real-World Examples
Apache HTTP Server 2.4.49/2.4.50 (CVE-2021-41773 & CVE-2021-42013)
A critical directory traversal flaw in Apache HTTP Server allowed unauthenticated attackers to map URLs to files outside the expected document root using URL-encoded dot-dot sequences (.%2e). The initial fix in 2.4.50 was immediately bypassed via double encoding (%%32%65), leading to CVE-2021-42013. When combined with mod_cgi, the vulnerability enabled remote code execution. Threat actors began mass exploitation within 24 hours, targeting an estimated 112,000 exposed servers. CISA added it to the Known Exploited Vulnerabilities catalog.
Fortinet FortiOS SSL VPN (CVE-2018-13379)
A directory traversal in the Fortinet SSL VPN web portal allowed unauthenticated attackers to read arbitrary system files by crafting HTTP requests with specially formed path segments. Attackers extracted VPN session tokens to hijack authenticated sessions. In November 2020, a threat actor published a list of nearly 50,000 vulnerable Fortinet VPN IP addresses, and in September 2021, credentials for 87,000 devices were leaked on a dark web forum. The vulnerability was exploited by APT groups including APT29 (Cozy Bear) and was cited in a joint FBI/CISA advisory.
Citrix ADC/Gateway (CVE-2019-19781) β 'Shitrix'
A directory traversal vulnerability in Citrix Application Delivery Controller and Gateway allowed unauthenticated remote code execution. Attackers traversed to a writable template directory and planted a malicious XML file that was then rendered by the Perl-based template engine, achieving command execution. Over 80,000 organizations in 158 countries were vulnerable. Exploitation began on January 10, 2020 β within weeks of PoC release β and was used to deploy ransomware (including REvil and DoppelPaymer) against healthcare, government, and financial institutions.
Impact & Risk Assessment
Directory traversal is classified as Critical because it collapses the most fundamental security boundary in web applications: file system isolation. A successful attack yields immediate access to configuration files containing database credentials, API secrets, and encryption keys. On cloud-hosted workloads, it exposes instance metadata endpoints and service account tokens, enabling full cloud account takeover. When write access is achievable, the path to remote code execution is direct. Historical data from vulnerability disclosure programs shows that directory traversal consistently ranks among the top 5 most frequently reported vulnerability classes in bug bounty programs. The attack requires no authentication, no special tooling, and often bypasses perimeter defenses β making it accessible to low-skill attackers while remaining devastating in impact.
How to Detect Directory Traversal Attack
Deploy multi-layered detection: (1) WAF rules matching ../ and its encoded variants (%2e%2e, %252e%252e, %c0%ae%c0%ae, ....//), null bytes (%00), and references to known sensitive paths (/etc/passwd, /etc/shadow, /proc/self, win.ini, web.config); (2) Application-level logging of all file system operations with the resolved canonical path, alerting when any resolved path falls outside the expected base directory; (3) Runtime Application Self-Protection (RASP) intercepting file I/O system calls and blocking out-of-bounds access in real time; (4) Network-level IDS/IPS signatures for traversal patterns in HTTP traffic; (5) File integrity monitoring (AIDE, OSSEC) on critical system files to detect unauthorized reads via access-time changes. Correlate traversal attempts with the requesting IP's threat intelligence score to prioritize incident response.
How to Prevent Directory Traversal Attack
Adopt a defense-in-depth strategy: (1) Never construct file paths from user input β use an allowlist or indirect reference map (user submits an ID, server maps it to a pre-defined path); (2) If dynamic paths are unavoidable, resolve the canonical path (realpath() in C/PHP, Path.GetFullPath() in .NET, os.path.realpath() in Python, path.resolve() in Node.js) and verify it starts with the expected base directory using a strict prefix check; (3) Reject any input containing path separators (/, \), traversal sequences, null bytes, or URL-encoded variants β do this after decoding; (4) Use OS-level isolation: chroot jails, Linux namespaces, Docker containers with read-only root filesystems, or AppArmor/SELinux profiles restricting the process's file access; (5) Apply the principle of least privilege β the web server process should own and access only its document root; (6) Deploy a WAF with comprehensive traversal signatures, including encoded and Unicode variants; (7) On Windows IIS, disable short filename (8.3) generation to prevent PROGRA~1-style traversal.
Code Examples
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
// VULNERABLE: user input directly concatenated into file path
app.get('/files/:name', (req, res) => {
const filePath = path.join(__dirname, 'uploads', req.params.name);
// No validation β attacker uses: /files/..%2f..%2f..%2fetc%2fpasswd
fs.readFile(filePath, (err, data) => {
if (err) return res.status(404).send('Not found');
res.send(data);
});
});
// Attacker requests:
// GET /files/....//....//....//etc/passwd
// GET /files/%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
// GET /files/..%252f..%252f..%252fetc%252fpasswd (double encoding)
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
const UPLOAD_DIR = path.resolve(__dirname, 'uploads');
app.get('/files/:name', (req, res) => {
const fileName = req.params.name;
// 1. Reject any path separators or traversal sequences
if (/[\/\\]/.test(fileName) || fileName.includes('..')) {
return res.status(400).json({ error: 'Invalid filename' });
}
// 2. Resolve the canonical (real) path
const resolved = path.resolve(UPLOAD_DIR, fileName);
// 3. Strict prefix check β must be inside UPLOAD_DIR
if (!resolved.startsWith(UPLOAD_DIR + path.sep)) {
return res.status(403).json({ error: 'Access denied' });
}
// 4. Verify file exists and is a regular file (not symlink to outside)
fs.lstat(resolved, (err, stats) => {
if (err || !stats.isFile()) {
return res.status(404).json({ error: 'Not found' });
}
// 5. Check the real path of symlinks too
const real = fs.realpathSync(resolved);
if (!real.startsWith(UPLOAD_DIR + path.sep)) {
return res.status(403).json({ error: 'Access denied' });
}
res.sendFile(real);
});
});
package main
import (
"net/http"
"os"
"path/filepath"
"strings"
)
func safeFileHandler(baseDir string) http.HandlerFunc {
absBase, _ := filepath.Abs(baseDir)
return func(w http.ResponseWriter, r *http.Request) {
// Clean the requested path
reqPath := filepath.Clean(r.URL.Path)
// Resolve the full path
fullPath := filepath.Join(absBase, reqPath)
absPath, err := filepath.Abs(fullPath)
if err != nil {
http.Error(w, "Invalid path", http.StatusBadRequest)
return
}
// Verify the resolved path is within the base directory
if !strings.HasPrefix(absPath, absBase+string(os.PathSeparator)) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// Resolve symlinks and re-check
realPath, err := filepath.EvalSymlinks(absPath)
if err != nil {
http.Error(w, "Not found", http.StatusNotFound)
return
}
if !strings.HasPrefix(realPath, absBase+string(os.PathSeparator)) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
http.ServeFile(w, r, realPath)
}
}
func main() {
http.HandleFunc("/files/", safeFileHandler("./uploads"))
http.ListenAndServe(":8080", nil)
}
Frequently Asked Questions
PowerWAF automatically blocks Directory Traversal Attack at the edge.
Deploy in minutes. No code changes required. Free plan available.
Free plan spots are limited Β· No credit card required