Lack of data validation - Path Traversal - Scala

Lack of data validation - Path Traversal - Scala

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.

1. Sanitizing the fileName parameter: The fileName parameter is sanitized by removing any characters that are not alphanumeric, a period (.), or a hyphen (-). This is done using the replaceAll 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 the fileName.

                val sanitizedFileName = fileName.replaceAll("[^a-zA-Z0-9.-]", "")
    
            


2. 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.

References