Skip to content
Pro Analyzer — Available with ShieldCI Pro

Directory Traversal Analyzer

Analyzer IDCategorySeverityTime To Fix
directory-traversal🛡️ SecurityHigh25 minutes

What This Checks

This analyzer detects path traversal vulnerabilities where user input constructs file paths without proper validation, allowing attackers to escape intended directories using sequences like ../.

Detected Vulnerable Patterns:

PHP File & Directory Operations (High / Critical)

  • file_get_contents($request->input('path')) - reads an arbitrary file from the filesystem
  • fopen($_GET['file'], 'r') - opens a user-controlled file path
  • unlink($request->input('name')) - deletes a user-specified file
  • Also covers: file_put_contents(), readfile(), rename(), copy(), move_uploaded_file(), scandir(), opendir(), glob()

Include / Require with User Input (Critical)

  • include $request->input('module') - remote or local file inclusion
  • require "{$base}/{$userModule}" - dynamic include without path validation

Laravel Storage & File Facades (High)

  • Storage::get($request->input('path')) - reads an arbitrary storage path
  • Storage::disk($request->input('disk')) - switches storage disk, bypassing root directory restrictions
  • File::delete($_GET['path']) - deletes a user-specified file via File facade
  • Also covers all Storage:: methods (put, delete, exists, download, path, url, temporaryUrl) and File:: methods (get, copy, move, put, append, link) with user input

File Uploads (High)

  • $file->store($request->input('dir')) - stores upload in user-controlled directory
  • $file->storeAs('uploads', $request->input('name')) - user-controlled filename without basename() protection
  • response()->download($request->input('path')) - serves an arbitrary file for download
  • response()->file($request->input('path')) - serves an arbitrary file inline
  • symlink($_GET['target'], ...) - creates a symlink to an attacker-controlled path
  • ->open($_GET['archive']) / ->extractTo($_GET['dest']) - zip-slip archive extraction
  • str_replace('../', '', $input) - naive traversal strip, bypassed with ....//, %2e%2e%2f, or mixed separators

Why It Matters

Directory traversal (also known as path traversal or dot-dot-slash attacks) allows attackers to escape intended directories and access sensitive files:

  • Credential Theft - Read .env, config/database.php, SSH keys, and API credentials
  • Source Code Disclosure - Access application source code to find further vulnerabilities
  • System File Access - Read /etc/passwd, /etc/shadow, or Windows system files
  • Remote File Inclusion (RFI) - Include malicious files from external servers via include/require
  • Local File Inclusion (LFI) - Execute arbitrary PHP code by including uploaded files or log files
  • Data Manipulation - Write or delete critical application files
  • Privilege Escalation - Overwrite configuration files to gain admin access

A single traversal vulnerability can compromise the entire server, especially when combined with file inclusion.

How to Fix

Quick Fix (5 minutes)

Use basename() and realpath() to validate paths:

Before (❌):

php
public function downloadFile(Request $request)
{
    $filename = $request->input('file');

    // VULNERABLE: User can send "../../.env" to access sensitive files
    return response()->download(storage_path("exports/{$filename}"));
}

After (✅):

php
public function downloadFile(Request $request)
{
    $filename = basename($request->input('file'));

    // SAFE: basename() strips directory traversal sequences
    $path = storage_path("exports/{$filename}");

    if (!file_exists($path)) {
        abort(404);
    }

    return response()->download($path);
}

Proper Fix (25 minutes)

Validate with realpath() and base directory check:

Before (❌):

php
public function readDocument(Request $request)
{
    $path = $request->input('path');

    // VULNERABLE: Direct user input in file operations
    $content = file_get_contents(storage_path("documents/{$path}"));

    return response($content);
}

After (✅):

php
public function readDocument(Request $request)
{
    $validated = $request->validate([
        'path' => 'required|string|max:255',
    ]);

    $baseDir = realpath(storage_path('documents'));
    $fullPath = realpath(storage_path("documents/{$validated['path']}"));

    // SAFE: Verify resolved path is within allowed directory
    if ($fullPath === false || !str_starts_with($fullPath, $baseDir)) {
        abort(403, 'Access denied');
    }

    $content = file_get_contents($fullPath);

    return response($content);
}

Secure Laravel Storage usage:

Before (❌):

php
public function getFile(Request $request)
{
    $path = $request->input('path');

    // VULNERABLE: User can traverse with "../" in path
    return Storage::get($path);
}

After (✅):

php
public function getFile(Request $request)
{
    $validated = $request->validate([
        'path' => ['required', 'string', 'max:255'],
    ]);

    $path = $validated['path'];

    // SAFE: Strip traversal sequences and validate
    if (str_contains($path, '..') || str_contains($path, "\0")) {
        abort(403, 'Invalid path');
    }

    if (!Storage::exists($path)) {
        abort(404);
    }

    return Storage::get($path);
}

Secure file uploads with basename() protection:

Before (❌):

php
public function uploadFile(Request $request)
{
    $request->validate(['file' => 'required|file']);
    $name = $request->input('filename');

    // VULNERABLE: User-controlled filename allows path traversal
    $request->file('file')->storeAs('uploads', $name);
}

After (✅):

php
public function uploadFile(Request $request)
{
    $request->validate([
        'file' => 'required|file|max:10240',
        'filename' => 'required|string|max:255',
    ]);

    // SAFE: basename() strips directory components
    $safeName = basename($request->input('filename'));

    // Even safer: generate a unique name
    $safeName = Str::uuid() . '.' . $request->file('file')->getClientOriginalExtension();

    $request->file('file')->storeAs('uploads', $safeName);
}

Best Practice: Whitelist Approach (✅✅):

php
public function downloadReport(Request $request)
{
    $validated = $request->validate([
        'report_id' => 'required|integer|exists:reports,id',
    ]);

    // SAFE: Look up path from database, never from user input
    $report = Report::findOrFail($validated['report_id']);

    $this->authorize('download', $report);

    return Storage::download($report->file_path);
}

References