Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Academy Path Traversal
Intermediate · 15 min

Path Traversal

Learn how ../../../ sequences escape intended directories and how to lock down file access.

1 The ../ Attack

Path traversal (also called directory traversal) allows attackers to read files outside the intended directory by inserting ../ sequences into file path parameters.

# VULNERABLE
filename = request.args.get('file')
path = '/var/app/uploads/' + filename
with open(path) as f:
    return f.read()
# Attacker: ?file=../../etc/passwd → reads /etc/passwd

URL-encoded variants (%2e%2e%2f) and double-encoded variants can bypass naive string checks. The attack works on Windows too with .. separators.

2 Canonicalization — The Fix

Resolve the full canonical path and verify it starts with the allowed base directory:

import os, pathlib

BASE = pathlib.Path('/var/app/uploads').resolve()

def safe_open(filename: str) -> str:
    # Resolve resolves symlinks and normalizes ../
    full = (BASE / filename).resolve()
    # Verify it's still inside BASE
    full.relative_to(BASE)  # raises ValueError if outside
    return full.read_text()

The relative_to() call raises ValueError if the resolved path escapes the base directory — even after symlinks and encoding tricks.

3 Avoid Dynamic File Serving

The safest approach is to avoid serving files by user-supplied paths at all. Instead, map user-visible identifiers (like numeric IDs or opaque tokens) to file paths server-side:

FILE_MAP = {
    'report-jan': '/var/reports/2024-01.pdf',
    'report-feb': '/var/reports/2024-02.pdf',
}

def serve_report(name: str):
    path = FILE_MAP.get(name)
    if path is None:
        abort(404)
    return send_file(path)

With an allowlist map, there is no path to traverse — the user can only access files you explicitly permit.

Knowledge Check

0/3 correct
Q1

What does the sequence ../ accomplish in a path traversal attack?

Q2

A developer strips all ../ substrings from user input before building the file path. Why might this still be bypassable?

Q3

What Python function resolves symlinks and normalizes a path so you can safely compare it to a base directory?

Code Exercise

Secure the File Serving Function

This function reads a file from the uploads directory using user input without any path validation. Add canonicalization to ensure the resolved path stays within BASE_DIR.

python