Skip to main content

Command Palette

Search for a command to run...

Directory Traversal Attack

Published
3 min read

A Directory Traversal Attack (also known as Path Traversal) is a security vulnerability that allows an attacker to access files and directories outside the intended directory. This can lead to unauthorized access to sensitive files such as configuration files, password files, or source code.

Why it happens

It occurs when user input is used to construct file paths without proper validation or sanitization, allowing ../ (parent directory) patterns to "traverse" directories.


Vulnerable Example in FastAPI

from fastapi import FastAPI, Request
from fastapi.responses import FileResponse
import os

app = FastAPI()

BASE_DIR = "/app/files"

@app.get("/read-file")
def read_file(file_name: str):
    file_path = os.path.join(BASE_DIR, file_name)

    if not os.path.exists(file_path):
        return {"error": "File not found"}

    return FileResponse(file_path)

Attack Scenario

If an attacker sends:

GET /read-file?file_name=../../etc/passwd

Then file_path becomes:

/app/files/../../etc/passwd -> /etc/passwd

This allows reading arbitrary files on the system.


Secure Version: Prevent Directory Traversal

from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from pathlib import Path

app = FastAPI()

BASE_DIR = Path("/app/files").resolve()

@app.get("/read-file")
def read_file(file_name: str):
    requested_path = (BASE_DIR / file_name).resolve()

    # Check if the resolved path starts with the BASE_DIR
    if not str(requested_path).startswith(str(BASE_DIR)):
        raise HTTPException(status_code=403, detail="Access denied")

    if not requested_path.exists():
        raise HTTPException(status_code=404, detail="File not found")

    return FileResponse(requested_path)

Key Defenses

  • Use Path.resolve() to normalize and resolve symlinks.

  • Always check if the resolved path starts with the base directory.

  • Do not trust user input for file paths.

  • Consider using a whitelist of allowed filenames if possible.


Let me know if you'd like to test this securely using dummy files.

Great — here's how to safely test directory traversal in FastAPI with dummy files.


Step-by-Step Demo Setup

Directory Structure:

project/
├── main.py
└── files/
    ├── public.txt
    └── secret.txt

1. Create files

Create a folder files/:

mkdir files
echo "This is a public file" > files/public.txt
echo "This is a secret file" > files/secret.txt

2. main.py (Safe FastAPI app)

from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from pathlib import Path

app = FastAPI()

BASE_DIR = Path("files").resolve()

@app.get("/read-file")
def read_file(file_name: str):
    requested_path = (BASE_DIR / file_name).resolve()

    if not str(requested_path).startswith(str(BASE_DIR)):
        raise HTTPException(status_code=403, detail="Access denied")

    if not requested_path.exists():
        raise HTTPException(status_code=404, detail="File not found")

    return FileResponse(requested_path)

3. Run the server

uvicorn main:app --reload

4. Try a safe request

GET http://localhost:8000/read-file?file_name=public.txt

Response:

This is a public file

5. Try a directory traversal attempt

GET http://localhost:8000/read-file?file_name=../files/secret.txt

Response:

{
  "detail": "Access denied"
}

Even though ../files/secret.txt is technically a valid path, it's outside the resolved base and thus blocked.


Extra Test Case

Try accessing an OS-sensitive file if you’re on Linux/macOS:

GET /read-file?file_name=../../etc/passwd

You'll get:

{
  "detail": "Access denied"
}

Let me know if you'd like to test with uploads or download APIs too!