Improper directory path restriction (path traversal)

Restrict file access to an approved directory

Description

Path traversal occurs when an application uses input as a file path without enforcing the allowed directory boundary. An attacker can use relative components such as ../ or ..\\ to reach files outside that boundary.

Potential impact

The effects depend on the application's file permissions and read/write operations.

  • Disclosure of system files or another user's data
  • Unauthorized reading or modification of data and configuration
  • Code execution if the application executes an attacker-controlled file

Remediation

  1. Avoid using input directly as a path. Validate it and restrict access to approved directories.
  2. Combine the path with Path.resolve(), normalize it and compare the Path components with the base path. resolve() alone does not enforce confinement. For existing files, use toRealPath() to resolve symbolic links and check the actual destination as well.
  3. If an attacker can modify directories between validation and use, restrict directory permissions and consider directory-relative APIs such as SecureDirectoryStream.
  4. Generate server-owned filenames, such as UUID-based names, where users do not need to choose a path.

Examples

Before

java
private static final String BASE_PATH ="/restricted/directory";

public String readFile(String filePath) throws IOException {
    Path file = Paths.get("/restricted/directory/" + filePath);
    return new String(Files.readAllBytes(file));
}

The second argument to File(parent, child) is also part of the path. Even with a fixed parent, .. components in an untrusted child can escape the base directory when the path is normalized.

java
import java.io.File;
import javax.servlet.http.HttpServletRequest;

public final class UnsafeUploadPath {
    private static final File UPLOAD_DIR = new File("/var/app/uploads");

    public File destination(HttpServletRequest request) {
        String child = request.getParameter("name");

        // Unsafe: the second constructor argument is also an attacker-controlled path.
        return new File(UPLOAD_DIR, child);
    }
}

After

This Java 11 or later example assumes attackers cannot modify the base directory or paths inside it. Apply file-size limits and appropriate responses to invalid input separately.

java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;

public final class SafeFileReader {
    private static final Path BASE_PATH = Path.of("/restricted/directory");

    public String readFile(String filePath) throws IOException {
        Path base = BASE_PATH.toRealPath();
        Path supplied = Path.of(filePath);
        if (supplied.isAbsolute()) {
            throw new SecurityException("Absolute paths are not allowed.");
        }

        Path candidate = base.resolve(supplied).normalize();
        if (!candidate.startsWith(base)) {
            throw new SecurityException("Unauthorized access attempt detected.");
        }

        // Resolve existing symbolic links and require the actual target to remain inside the base directory.
        Path requestedPath = candidate.toRealPath();
        if (!requestedPath.startsWith(base)
                || !Files.isRegularFile(requestedPath, LinkOption.NOFOLLOW_LINKS)) {
            throw new SecurityException("Unauthorized access attempt detected.");
        }
        return Files.readString(requestedPath, StandardCharsets.UTF_8);
    }
}

The first examples use untrusted paths directly, including new File(parent, child). The revised example rejects absolute paths, compares normalized path components with Path.startsWith(Path), resolves links and checks the actual destination. It reads the checked requestedPath itself.

Related CVEs

References