The article Secure Coding: Preventing Unauthorized Access via Path Traversal (CWE-22) describes the threats that arise in software development due to the already critical vulnerability CWE-22 (Path Traversal) and what measures developers can take against it. Can. The following is specifically about how tools from the Java New I/O (NIO) package can be used to address the risks associated with CWE-22. Best practices are used step by step to show how the robust NIO tools for file and path management can be used to specifically prevent path traversal vulnerabilities.
Advertisement
Sven has been programming Java for over 15 years in industrial projects since 1996 and in industries such as automotive, aerospace, insurance, banking, United Nations and the World Bank around the world. For over 10 years he has been a speaker at conferences and community events from the US to New Zealand, worked as developer advocate for JFrog and Vaadin and regularly writes articles for IT magazines and technology portals. Apart from his core subject of Core Java, he deals with TDD and secure coding practices.
normalize paths
Normalization removes all unnecessary elements from the path, for example “.” (current directory) and “..” (parent directory). This process helps ensure that paths are properly structured and prevents path traversal attacks.
Create base path: Define the base directory against which user input will be resolved. This should be the directory you want to restrict access to.
Path basePath = Paths.get("/var/www/uploads").normalize();
Resolve user input: Combine the base path with user-supplied input to create a complete path. This step is important to ensure that any relative paths specified by the user are interpreted in the context of the base path.
String userInput = request.getParameter("file");
Path resolvedPath = basePath.resolve(userInput);
Normalize the resolved path to remove all unnecessary elements. This step ensures that all “.” Or the “..” in the path can be resolved correctly.
Path normalizedPath = resolvedPath.normalize();
Validate the normalized path: Make sure that the normalized path starts from the base path. This check ensures that the path does not go outside the intended directory.
if (!normalizedPath.startsWith(basePath)) {
throw new SecurityException("Invalid file path: path traversal attempt detected.");
}
Check the path to make sure it resides within the base directory: Make sure the base directory is normalized in its simplest form. Combine the base directory with the user-supplied path, and then normalize the resulting path. Set the final normalized path to start from the base directory to ensure it doesn’t go outside the intended directory.
Use secure directory and file permissions

It is important to use secure directory and file permissions to ensure the security and integrity of your files and directories, especially when handling user input in Java applications.
Java NIO provides APIs for setting and checking file permissions. classes PosixFilePermissions
And PosixFileAttributeView
It can do.
Setting permissions on a directory
To set permissions on a directory, you can do this Files.createDirectories
And PosixFilePermissions
Use:
import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;
import java.io.IOException;
public class SecureFileHandler {
/**
* Creates a directory with secure permissions.
*
* @param dirPath The path of the directory to create.
* @throws IOException if an I/O error occurs.
*/
public static void createSecureDirectory(Path dirPath) throws IOException {
Set perms = PosixFilePermissions.fromString("rwxr-x---");
FileAttribute> attr
= PosixFilePermissions.asFileAttribute(perms);
Files.createDirectories(dirPath, attr);
}
/**
* Example usage of creating a secure directory.
*
* @param args Command line arguments.
*/
public static void main(String() args) {
Path dirPath = Paths.get("/var/www/uploads");
try {
createSecureDirectory(dirPath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Setting permissions for files
Similarly, you can set permissions for files using the method Files.setPosixFilePermissions
to install:
import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.io.IOException;
import java.util.Set;
public class SecureFileHandler {
/**
* Sets secure permissions for a file.
*
* @param filePath The path of the file.
* @throws IOException if an I/O error occurs.
*/
public static void setSecureFilePermissions(Path filePath) throws IOException {
Set perms = PosixFilePermissions.fromString("rw-r-----");
Files.setPosixFilePermissions(filePath, perms);
}
/**
* Example usage of setting secure file permissions.
*
* @param args Command line arguments.
*/
public static void main(String() args) {
Path filePath = Paths.get("/var/www/uploads/example.txt");
try {
setSecureFilePermissions(filePath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
check file permissions
Before performing file operations, it is important to check whether the file or directory has the correct permissions. Here’s how you can do it:
import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.PosixFilePermission;
import java.io.IOException;
import java.util.Set;
public class SecureFileHandler {
/**
* Checks if a file has the required permissions.
*
* @param filePath The path of the file.
* @param requiredPerms The required permissions.
* @return True if the file has the required permissions, false otherwise.
* @throws IOException if an I/O error occurs.
*/
public static boolean hasRequiredPermissions(Path filePath,
Set requiredPerms)
throws IOException {
Set perms = Files.getPosixFilePermissions(filePath);
return perms.containsAll(requiredPerms);
}
/**
* Example usage of checking file permissions.
*
* @param args Command line arguments.
*/
public static void main(String() args) {
Path filePath = Paths.get("/var/www/uploads/example.txt");
Set requiredPerms = PosixFilePermissions.fromString("rw-r-----");
try {
if (hasRequiredPermissions(filePath, requiredPerms)) {
System.out.println("File has the required permissions.");
} else {
System.out.println("File does not have the required permissions.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Practical Security Considerations
Restrict write access: Make sure write access is limited to essential users and processes. This reduces the risk of unauthorized changes.
Read access: Set read-only permissions on files that do not need to be modified to prevent unauthorized changes.
Execution Permissions: Be careful when granting execution permissions. Allow execution only to required users and ensure scripts or executables are secure.
Owner and group permissions: Set appropriate owner and group permissions. Make sure that sensitive files and directories are owned by the correct user and group.
Symbolic link: Avoid following symbolic links if possible. This is the only way for attackers to bypass security controls by using symbolic link attacks.
Uses of Umask: configure value umask
To control the default permission settings for newly created files and directories. This ensures basic security.
use of classes Path
And Files
Java NIO and the correct permission settings can greatly improve the security of your file and directory operations. These practices help reduce the risk of unauthorized access and modification and protect your application from potential path traversal (CWE-22) vulnerabilities.
Treat symlinks safely
Secure operation of symbolic links (symlinks) is important to prevent potential security risks such as access control bypassing and path traversal attacks. Attackers can exploit symlinks to gain unauthorized access to files and directories. The following best practices will help you ensure safe handling of symlinks in Java using the New I/O (NIO) API:
Avoid the following symlinks: use option NOFOLLOW_LINKS
To avoid following symlinks while performing file operations. This ensures that operations are performed on the symlink and not on the target file or directory.
Validate the target of the symlink: If your application must follow a symlink, validate the destination of the symlink to ensure that it points to a valid location.
Find symlink: Explicitly check if a path is a symlink and behave accordingly.
Example 1: How to avoid the following symlinks in file operations with Java NIO:
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;
public class SecureSymlinkHandler {
/**
* Checks if the given path is a symbolic link.
*
* @param path The path to check.
* @return True if the path is a symbolic link, false otherwise.
* @throws IOException if an I/O error occurs.
*/
public static boolean isSymlink(Path path) throws IOException {
return Files.isSymbolicLink(path);
}
/**
* Safely deletes a file without following symbolic links.
*
* @param path The path to the file to delete.
* @throws IOException if an I/O error occurs.
*/
public static void safeDelete(Path path) throws IOException {
if (isSymlink(path)) {
throw new SecurityException("Refusing to delete symbolic link: " + path);
}
Files.delete(path);
}
/**
* Safely reads a file's attributes without following symbolic links.
*
* @param path The path to the file.
* @return The file's attributes.
* @throws IOException if an I/O error occurs.
*/
public static BasicFileAttributes safeReadAttributes(Path path) throws IOException {
return Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
}
public static void main(String() args) {
Path path = Paths.get("/var/www/uploads/example.txt");
try {
if (isSymlink(path)) {
System.out.println("Path is a symbolic link.");
} else {
System.out.println("Path is not a symbolic link.");
BasicFileAttributes attrs = safeReadAttributes(path);
System.out.println("File size: " + attrs.size());
safeDelete(path);
System.out.println("File deleted safely.");
}
} catch (IOException | SecurityException e) {
e.printStackTrace();
}
}
}
Example 2: Validate the target of a symlink. If your application needs to follow symlinks, validate their targets to make sure they point to an acceptable (safe) location:
import java.nio.file.*;
import java.io.IOException;
public class SecureSymlinkHandler {
/**
* Validates that the symlink's target is within the allowed base directory.
*
* @param symlink The symbolic link to validate.
* @param baseDir The allowed base directory.
* @throws IOException if an I/O error occurs or if validation fails.
*/
public static void validateSymlinkTarget(Path symlink, Path baseDir) throws IOException {
if (!Files.isSymbolicLink(symlink)) {
throw new IllegalArgumentException("Path is not a symbolic link: " + symlink);
}
Path target = Files.readSymbolicLink(symlink).normalize();
Path resolvedTarget = baseDir.resolve(target).normalize();
if (!resolvedTarget.startsWith(baseDir)) {
throw new SecurityException("Invalid symlink target: " + resolvedTarget);
}
}
public static void main(String() args) {
Path symlink = Paths.get("/var/www/uploads/symlink");
Path baseDir = Paths.get("/var/www/uploads").normalize();
try {
validateSymlinkTarget(symlink, baseDir);
System.out.println("Symlink target is valid and within the allowed base directory.");
} catch (IOException | SecurityException e) {
e.printStackTrace();
}
}
}
Allow only trusted users to create symlinks. This reduces the risk of symlinks being used for malicious purposes. Periodically check symbolic links in your applications to ensure that they are not pointing to unauthorized locations. Use libraries that are symlink aware and handle them safely. This can help reduce the risk of unintended symlink results. To minimize the impact of potential symlink-related vulnerabilities, ensure that your application runs with the minimum required permissions. Use multiple layers of security controls to protect against symlink attacks. This includes file system permissions, application-level checking, and regular monitoring.
Fully validate user input
It is important to rigorously validate user input to ensure the security and integrity of an application. Proper input validation helps prevent a variety of attacks including path traversal (CWE-22), SQL injection, cross-site scripting (XSS), and more. The following best practices and techniques will help you implement strict user input validation in Java applications:
- Whitelist validation: Allow only inputs that match a predefined set of acceptable values. This is the most secure form of verification.
- Blacklist validation: Reject input that contains dangerous characters or patterns. This approach is less secure than whitelisting, but can be used as an additional measure.
- Length check: Make sure the inputs are within the expected length range. This prevents buffer overflows and denial of service (DoS) attacks.
- Data type checking: Make sure the inputs match the expected data type (e.g. integer, date).
- Encoding and escapes: Encode and escape input to prevent injection attacks in different contexts (e.g. HTML, SQL).
- Canonicalization: Convert the input to a standard format before validation. This helps to compare and process inputs safely.
- Restrict allowed characters: Validate file names and paths against a whitelist of allowed characters. Reject any input that contains characters that could change the path structure (e.g. .., /, \).
- Check the path against the base directory: Make sure that the resolved and normalized path starts from the desired base directory.
Below is an example implementation in Java that combines these principles and techniques to validate user input, specifically for file paths:
import java.nio.file.*;
import java.util.Set;
import java.util.HashSet;
import java.util.logging.Logger;
import java.io.IOException;
import java.util.regex.Pattern;
public class SecureInputValidator {
private static final Logger logger = Logger.getLogger(SecureInputValidator.class.getName());
private static final Set ALLOWED_EXTENSIONS = new HashSet<>();
static {
ALLOWED_EXTENSIONS.add(".txt");
ALLOWED_EXTENSIONS.add(".jpg");
ALLOWED_EXTENSIONS.add(".png");
ALLOWED_EXTENSIONS.add(".pdf");
}
/**
* Validates the user-provided file name against a whitelist of allowed characters and extensions.
*
* @param fileName The user-provided file name.
* @throws IllegalArgumentException if the file name is invalid.
*/
public static void validateFileName(String fileName) throws IllegalArgumentException {
if (fileName == null || fileName.isEmpty()) {
throw new IllegalArgumentException("File name cannot be null or empty.");
}
// Check for invalid characters
Pattern pattern = Pattern.compile("(^a-zA-Z0-9._-)");
if (pattern.matcher(fileName).find()) {
throw new IllegalArgumentException("File name contains invalid characters.");
}
// Check for allowed file extensions
boolean validExtension = ALLOWED_EXTENSIONS.stream().anyMatch(fileName::endsWith);
if (!validExtension) {
throw new IllegalArgumentException("File extension is not allowed.");
}
}
/**
* Validates the user-provided path to ensure it stays within the base directory.
*
* @param baseDir The base directory.
* @param userInput The user-provided input.
* @return The validated and normalized path.
* @throws SecurityException if a path traversal attempt is detected.
* @throws IllegalArgumentException if the file name is invalid.
* @throws IOException if an I/O error occurs.
*/
public static Path getSecureFilePath(String baseDir, String userInput) throws SecurityException, IllegalArgumentException, IOException {
validateFileName(userInput);
// Normalize the base directory
Path basePath = Paths.get(baseDir).normalize();
// Resolve the user input against the base directory and normalize the result
Path resolvedPath = basePath.resolve(userInput).normalize();
// Validate that the resolved path starts with the base directory
if (!resolvedPath.startsWith(basePath)) {
logSuspiciousActivity(userInput);
throw new SecurityException("Invalid file path: path traversal attempt detected.");
}
return resolvedPath;
}
/**
* Logs suspicious activity for further analysis.
*
* @param userInput The suspicious user input.
*/
private static void logSuspiciousActivity(String userInput) {
logger.warning("Suspicious file access attempt: " + userInput);
}
/**
* Example usage of the secure file path validation.
*
* @param args Command line arguments.
*/
public static void main(String() args) {
String baseDir = "/var/www/uploads";
String userInput = "example.txt";
try {
Path filePath = getSecureFilePath(baseDir, userInput);
System.out.println("Validated file path: " + filePath);
} catch (SecurityException | IllegalArgumentException | IOException e) {
e.printStackTrace();
}
}
}
