👨‍💻
Application Security Handbook
  • Application Security Handbook
  • Web Application
    • Authentication
      • Authentication with Login and Password
      • Authentication with Phone Number
      • OAuth 2.0 Authentication
      • Multi-factor Authentication
      • Default Passwords
      • Password Change
      • Password Policy
      • Password Reset
      • Password Storage
      • One Time Password (OTP)
      • Email Address Confirmation
    • Authorization
    • Concept of Trusted Devices
    • Content Security Policy (CSP)
    • Cookie Security
    • Cryptography
      • Cryptographic Keys Management
      • Encryption
      • Hash-based Message Authentication Code (HMAC)
      • Hashing
      • Random Generators
      • Universal Unique Identifier (UUID)
    • Error and Exception Handling
    • File Upload
    • Input Validation
    • JSON Web Token (JWT)
    • Logging and Monitoring
    • Output Encoding
    • Regular Expressions
    • Sensitive Data Management
    • Session Management
    • Transport Layer Protection
    • Vulnerability Mitigation
      • Brute-force
      • Command Injection
      • Cross-Site Request Forgery (CSRF)
      • Cross-Site Scripting (XSS)
      • Mass Parameter Assignment
      • Parameter Pollution
      • Path Traversal
      • Regular Expression Denial of Service (ReDoS)
      • SQL Injection (SQLi)
      • XML External Entity (XXE) Injection
Powered by GitBook
On this page
  • Overview
  • General
  • Implementing the canonical path validation
  • References
  1. Web Application
  2. Vulnerability Mitigation

Path Traversal

PreviousParameter PollutionNextRegular Expression Denial of Service (ReDoS)

Last updated 1 year ago

Overview

Path traversal (or directory traversal) is a vulnerability that allows an attacker to read or write arbitrary files on the server that is running an application.

For example, consider an application that loads images via some HTML like the following:

<img src="/load?filename=218.png">

The /load URL takes the filename parameter and returns the contents of the specified file. The image files themselves are stored on disk in the location /var/www/images/. To return an image, the application:

  1. appends the requested filename 218.png to the base directory /var/www/images/,

  2. uses a filesystem API to read the contents of the /var/www/images/218.png file.

This behaviour can be abused by an attacker. An attacker can request the following URL to retrieve an arbitrary file from the server:

https://vulnerable-website.local/load?filename=../../../etc/passwd

This causes the application to read from the following file path:

/var/www/images/../../../etc/passwd

As a result, the application will return to an attacker a content of the /etc/passwd.

You can find more details at .

This page contains recommendations for the implementation of protection against path traversal attacks.

General

  • Do not allow a user to control data that is passed to the file system API.

    • Use an allow list of paths to validate user-provided data if possible.

    • Include only alphanumeric characters in an allow list of characters.

    • If it is necessary to use special characters such as . or /, prevent the use of combinations represented as regular expressions below.

      \A\.\z
      \A\.\.\z
      \A\.\.[/\\]
      [/\\]\.\.\z
      [/\\]\.\.[/\\]
      \A/
      \A~
      \n
      \r
    • Do not rely solely on a block list validation, it can be bypassed in many cases.

  • Methods used to construct file paths can have non-intuitive behaviour. Make sure the methods you use work as expected with data like this:

    .
    ..
    /
    ~/some/path
    /etc/passwd
    ../../../etc/passwd
    /../../../etc/passwd
    ../../../../../../../../../../../../etc/passwd
Clarification

For example, the Pathname.join method in Ruby, which joins pathnames, handles absolute names unintuitively.

require 'pathname'

p = Pathname.new('tmp')

user_controlled_input = 'etc/passwd'
print(p.join('log', user_controlled_input, 'foo'))
# => tmp/log/etc/passwd/foo

user_controlled_input = '/etc/passwd'
print(p.join('log', user_controlled_input, ''))
# => /etc/passwd

As can be seen, if user_controller_input contains an absolute path, Pathname.join will ignore everything up to the argument with the absolute path. In other words, it will allow an attacker to craft an arbitrary path.

  • Use a sandbox to obtain or save data.

Implementing the canonical path validation

package main

import (
    "fmt"
    "path"
    "strings"
)

func inTrustedBasePath(basePath string, userControlledPath string) bool {
    basePath = path.Join(basePath)
    fullPath := path.Join(basePath, userControlledPath)
    if !strings.HasPrefix(fullPath, basePath+"/") {
        return false
    }
    return true
}

func main() {
    basePath := "/tmp/path/to/base/folder"
    userControlledData := "nothing/dangerous.txt"
    fmt.Println(inTrustedBasePath(basePath, userControlledData))
    // => true

    userControlledData = "../../../path/traversal"
    fmt.Println(inTrustedBasePath(basePath, userControlledData))
    // => false
}
import java.io.*;

public class CanonicalPathValidation {

    public static void main(String []args){
        String basePath = "/tmp/path/to/base/folder";
        String userControlledData = "nothing/dangerous.txt";
        System.out.println(inTrustedBasePath(basePath, userControlledData));
        // => true

        String basePath = "/tmp/path/to/base/folder";
        String userControlledData = "../../../path/traversal";
        System.out.println(inTrustedBasePath(basePath, userControlledData));
        // => false
    }

    private static boolean inTrustedBasePath(String basePath, String userControlledPath) {
        try {
            File file = new File(basePath, userControlledPath);
            if (!file.getCanonicalPath().startsWith(basePath)) {
                return false;
            }
            return true;
        } catch (IOException e) {
            return false;
        }
    }
}
import os
from pathlib import Path


def in_trusted_base_path(base_path: str, user_controlled_path: str) -> bool:
    base_path = Path(base_path)
    full_path = Path(base_path, user_controlled_path)
    real_path = os.path.realpath(full_path)
    return Path(real_path).is_relative_to(base_path)


def main() -> None:
    base_path = '/tmp/path/to/base/folder'
    user_controlled_data = 'nothing/dangerous.txt'
    print(in_trusted_base_path(base_path, user_controlled_data))
    # => True

    base_path = '/tmp/path/to/base/folder'
    user_controlled_data = '../../../path/traversal'
    print(in_trusted_base_path(base_path, user_controlled_data))
    # => False

References

Generate random folder/file names using a UUID or random generator instead of relying on user-provided names, see the and pages.

If data can be controlled by a user, implement comprehensive input validation for all data that is passed to the file system API, see the page.

Make sure the canonicalized path starts with the expected base directory, see the section.

Use the function to get a canonical path.

Use the method to get a canonical path.

Use the function to get a canonical path.

Cryptography: Universal Unique Identifier (UUID)
Cryptography: Random Generators
Input Validation
path.Join
File.getCanonicalPath
os.path.realpath
GitLab Docs: Secure coding development guidelines
Implementing the canonical path validation
PortSwigger Web Security Academy: Directory traversal