Local file inclusion - Scala

Local file inclusion - Scala

Need

Prevention of unauthorized access to sensitive files on the server

Context

  • Usage of Scala for building scalable and high-performance applications
  • Usage of play.api.mvc for handling HTTP requests and responses in Play Framework
  • Usage of scala.io.Source for reading and processing input sources in Scala

Description

Non compliant code

        import play.api.mvc._
import scala.io.Source

class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {

  def readFile(filename: String) = Action { implicit request: Request[AnyContent] =>
    try {
      val file = Source.fromFile(filename)
      val content = file.getLines.mkString
      file.close()
      Ok(content)
    } catch {
      case _: Exception => NotFound
    }
  }
}
        
        

The above Scala code is a simple controller in a Play Framework application. The controller has a single action readFile, which takes a filename as a parameter and attempts to read the contents of that file from the server's file system. This is done using scala.io.Source.fromFile(filename), which opens the file and reads its content.

The vulnerability here is Local File Inclusion (LFI), which occurs when an application uses user-supplied input to construct a file path for file operations. In this case, the user-supplied input is the filename. An attacker can manipulate the filename parameter to read any file on the server. For example, by supplying ../../../../etc/passwd as the filename, an attacker could read the contents of the server's password file.

This is a serious security vulnerability as it can lead to information disclosure, denial of service, or even remote code execution if an attacker can use LFI to read sensitive files on the server, or to include and execute scripts that should not be directly accessible.

The vulnerability can be fixed by validating and sanitizing the user-supplied input and by limiting the files that can be read through the application.

Steps

  • Validate user input: Ensure that the filename provided by the user is a valid and safe value.
  • Sanitize user input: Remove any special characters or escape sequences from the filename to prevent path manipulation.
  • Use absolute paths: Instead of using relative paths, use absolute paths to access files on the server.
  • Restrict file access: Implement access controls to restrict the files that can be accessed by the application.
  • Implement file whitelisting: Maintain a whitelist of allowed files and only allow access to files in the whitelist.
  • Use file permissions: Set appropriate file permissions to restrict access to sensitive files.
  • Implement file upload/download functionality: Instead of directly reading files from the server, implement secure file upload/download functionality that ensures proper validation and sanitization of file paths.

Compliant code

        import play.api.mvc._
import scala.io.Source
import java.nio.file.{Paths, Files}

class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {

  private val allowedFiles = List("file1.txt", "file2.txt") // Whitelist of allowed files

  def readFile(filename: String) = Action { implicit request: Request[AnyContent] =>
    if (!allowedFiles.contains(filename)) {
      Forbidden("Access to this file is not allowed.")
    } else {
      val safeFilename = filename.replaceAll("[^A-Za-z0-9.]", "") // Sanitize filename
      val absolutePath = Paths.get("/absolute/path/to/files", safeFilename).toString // Use absolute path

      if (!Files.exists(Paths.get(absolutePath))) {
        NotFound("File not found.")
      } else {
        try {
          val file = Source.fromFile(absolutePath)
          val content = file.getLines.mkString
          file.close()
          Ok(content)
        } catch {
          case _: Exception => InternalServerError("An error occurred while reading the file.")
        }
      }
    }
  }
}
        
        

The updated code includes several measures to prevent local file inclusion vulnerabilities:

1. File Whitelisting: A list of allowed files ( allowedFiles) is maintained. If the filename provided by the user is not in this list, a Forbidden response is returned.

2. Input Sanitization: The filename provided by the user is sanitized to remove any special characters or escape sequences that could be used for path manipulation. This is done using the replaceAll method.

3. Absolute Paths: Instead of using the relative path provided by the user, an absolute path is constructed using the sanitized filename. This prevents the user from accessing files outside of the intended directory.

4. File Existence Check: Before attempting to read the file, a check is performed to ensure that the file exists. If the file does not exist, a NotFound response is returned.

5. Error Handling: If an error occurs while reading the file (for example, if the file is not readable), an InternalServerError response is returned. This prevents the application from crashing and provides a more informative error message to the user.

These measures significantly reduce the risk of local file inclusion vulnerabilities in the application.

References