Introduction

A directory traversal (or path traversal) vulnerability allows attackers to access files outside the intended directory by using ../ sequences in file path parameters. When exploited, attackers can read sensitive files like /etc/passwd, /etc/shadow, application configuration files containing database credentials, SSH keys, or source code. Immediate containment, damage assessment, and remediation are required to prevent further unauthorized access.

Symptoms

  • Web server access logs show requests with ../ patterns:
  • `
  • 192.168.1.100 - - [09/Apr/2026:03:15:22 +0000] "GET /files/../../../etc/passwd HTTP/1.1" 200 1234
  • 192.168.1.100 - - [09/Apr/2026:03:15:25 +0000] "GET /files/../../../etc/shadow HTTP/1.1" 200 892
  • 192.168.1.100 - - [09/Apr/2026:03:15:30 +0000] "GET /files/../../../app/.env HTTP/1.1" 200 456
  • 192.168.1.100 - - [09/Apr/2026:03:16:01 +0000] "GET /files/../../../home/deploy/.ssh/id_rsa HTTP/1.1" 200 1679
  • `
  • Or encoded variants:
  • `
  • GET /files/..%2f..%2f..%2fetc/passwd HTTP/1.1
  • GET /files/..%252f..%252f..%252fetc/passwd HTTP/1.1
  • `
  • Response status 200 indicates the files were successfully read
  • Attack IP accessed configuration files, credentials, and SSH keys

Common Causes

  • File download endpoint does not sanitize the filename parameter
  • Application uses user input directly in file path construction
  • Lack of chroot or sandbox for file serving functionality
  • Web server configured to serve files from a directory without path validation
  • Application framework's file serving middleware misconfigured

Step-by-Step Fix

Phase 1: Contain the Breach

  1. 1.Block the attacking IP immediately:
  2. 2.```bash
  3. 3.# Block at firewall level
  4. 4.sudo iptables -A INPUT -s 192.168.1.100 -j DROP
  5. 5.# Or with ufw:
  6. 6.sudo ufw deny from 192.168.1.100
  7. 7.`
  8. 8.Rotate all potentially exposed credentials:
  9. 9.```bash
  10. 10.# The attacker likely read these files:
  11. 11.# /etc/shadow - all user password hashes
  12. 12.# /app/.env - database credentials, API keys
  13. 13.# /home/deploy/.ssh/id_rsa - SSH private key

# Rotate database passwords psql -U postgres -c "ALTER USER app_user WITH PASSWORD 'new-secure-password';"

# Regenerate API keys aws iam create-access-key --user-name deploy # Then deactivate the old key aws iam update-access-key --access-key-id OLD_KEY_ID --status Inactive

# Regenerate SSH keys ssh-keygen -t ed25519 -f /home/deploy/.ssh/id_ed25519 -N "" # Update authorized_keys on all servers ```

Phase 2: Assess the Damage

  1. 1.Determine what files were accessed:
  2. 2.```bash
  3. 3.# Search access logs for traversal patterns
  4. 4.grep -E '\.\.\/|\.\.%2[fF]|%252[fF]' /var/log/nginx/access.log

# Check if the attacker escalated further grep "192.168.1.100" /var/log/auth.log # Look for SSH login attempts from this IP after the file read

# Check for new SSH keys added to authorized_keys cat /home/*/\.ssh/authorized_keys # Look for keys that do not match known deployments ```

  1. 1.Check for signs of lateral movement:
  2. 2.```bash
  3. 3.# Check for new user accounts
  4. 4.awk -F: '$3 >= 1000 {print $1}' /etc/passwd

# Check for new cron jobs crontab -l ls -la /etc/cron.d/ cat /var/spool/cron/crontabs/*

# Check for new SUID binaries find / -perm -4000 -type f 2>/dev/null | sort

# Check recently modified files find /etc /home /var/www -mtime -1 -type f 2>/dev/null ```

Phase 3: Patch the Vulnerability

  1. 1.Fix the file serving code:
  2. 2.```python
  3. 3.# BEFORE (vulnerable)
  4. 4.@app.route('/files/<path:filename>')
  5. 5.def serve_file(filename):
  6. 6.return send_file(f'/uploads/{filename}')

# AFTER (secure) import os from werkzeug.utils import safe_join

UPLOAD_DIR = os.path.abspath('/uploads')

@app.route('/files/<path:filename>') def serve_file(filename): # Resolve the full path filepath = os.path.abspath(safe_join(UPLOAD_DIR, filename))

# Verify the resolved path is within the upload directory if not filepath.startswith(UPLOAD_DIR): abort(403) # Forbidden

# Verify the file exists if not os.path.isfile(filepath): abort(404)

return send_file(filepath) ```

  1. 1.Add WAF rules to block traversal attempts:
  2. 2.```nginx
  3. 3.# Nginx - block common traversal patterns
  4. 4.location ~* \.\./ {
  5. 5.return 403;
  6. 6.}

# Or use ModSecurity rule # SecRule ARGS "@contains ../" "id:1001,phase:2,deny,status:403" ```

Prevention

  • Use safe_join or os.path.realpath to validate file paths
  • Serve files through a dedicated CDN or object storage (S3) instead of the application
  • Implement allowlists for permitted file extensions and directories
  • Run the application under a dedicated user with minimal file system access
  • Use chroot or containers to limit file system scope
  • Monitor logs for ../ patterns and alert on detection
  • Conduct regular security audits of file handling endpoints