Lack of data validation - Path Traversal
Need
Implementation of proper input validation and sanitization to prevent path traversal attacks
Context
- Usage of Scala for building scalable and high-performance applications
- Usage of play.api.mvc for handling HTTP requests and responses in the Play Framework
- Usage of play.api.libs.Files for file handling in Play Framework
- Usage of java.nio.file.Paths for working with file paths in Java
Description
Non compliant code
import play.api.mvc._
import play.api.libs.Files
import java.nio.file.Paths
class ApplicationController extends Controller {
def downloadFile(fileName: String) = Action {
val file = Paths.get("/app/files/", fileName).toFile
if (file.exists) {
Ok.sendFile(file)
} else {
NotFound
}
}
}
The downloadFile
function in the ApplicationController
class takes a fileName
parameter from the user and uses it to construct a file path. This file path is then used to fetch a file from the server's file system and send it back to the user.
This code is vulnerable to Path Traversal attacks because it does not validate or sanitize the fileName
parameter before using it to construct the file path. An attacker can provide a fileName
parameter that includes special characters or sequences (like ../
) to traverse the file system and access files outside of the intended directory.
For example, an attacker can request /downloadFile?fileName=../../etc/passwd
to download the /etc/passwd
file, which contains sensitive information about every user on the system.
This vulnerability can lead to unauthorized access to sensitive data, corruption or deletion of data, or other unintended behavior. It is critical to always validate and sanitize user input before using it to interact with the file system.
Steps
- Implement input validation to ensure that the fileName parameter is safe and does not contain any malicious characters or sequences.
- Use a whitelist approach to validate the fileName parameter, allowing only specific characters or patterns.
- Sanitize the fileName parameter by removing any special characters or sequences that could be used for path traversal.
- Consider using a secure file storage mechanism that does not rely on constructing file paths based on user input.
- Implement proper access controls to restrict the files that can be accessed and downloaded by users.
- Regularly update and patch the software and its dependencies to address any known vulnerabilities.
Compliant code
import play.api.mvc._
import play.api.libs.Files
import java.nio.file.{Paths, Path, Files => JFiles}
import java.io.File
class ApplicationController extends Controller {
def downloadFile(fileName: String) = Action {
val sanitizedFileName = fileName.replaceAll("[^a-zA-Z0-9.-]", "")
val file = Paths.get("/app/files/", sanitizedFileName).normalize().toFile
val appDir = new File("/app/files/").getCanonicalPath
if (file.getCanonicalPath.startsWith(appDir) && file.exists) {
Ok.sendFile(file)
} else {
NotFound
}
}
}
The updated code includes a fix for the path traversal vulnerability. The fix involves sanitizing the fileName
parameter and validating the constructed file path.
Sanitizing the
fileName
parameter: ThefileName
parameter is sanitized by removing any characters that are not alphanumeric, a period (.), or a hyphen (-). This is done using thereplaceAll
method with a regular expression that matches any character that is not in the specified set. This helps to prevent any special characters or sequences that could be used for path traversal from being included in thefileName
.val sanitizedFileName = fileName.replaceAll("[^a-zA-Z0-9.-]", "")
Validating the constructed file path: The constructed file path is validated by normalizing it and checking that it starts with the expected directory path. Normalizing the path converts any relative path elements (like
..
) to their absolute path equivalents. Checking that the normalized path starts with the expected directory path ensures that the file path does not point to a location outside of the intended directory.val file = Paths.get("/app/files/", sanitizedFileName).normalize().toFile
val appDir = new File("/app/files/").getCanonicalPath
if (file.getCanonicalPath.startsWith(appDir) && file.exists) {
Ok.sendFile(file)
} else {
NotFound
}
This fix ensures that the downloadFile
method is not vulnerable to path traversal attacks, as it properly validates and sanitizes the fileName
parameter and the constructed file path.