Prevention of unauthorized access to sensitive files on the server
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.
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.