Enforce a limit on the number of files that can be uploaded within a specific time period and implement a mechanism to automatically delete older files to prevent excessive storage consumption.
import play.api.mvc._
import play.api.libs.Files
class FileController @Inject() (mcc: MessagesControllerComponents) extends MessagesAbstractController(mcc) {
def upload = Action(parse.multipartFormData) { request =>
request.body.file("picture").map { picture =>
val filename = picture.filename
val contentType = picture.contentType
picture.ref.moveTo(Paths.get(s"/tmp/picture/$filename"), replace = false)
Ok("File uploaded")
}.getOrElse {
Redirect(routes.HomeController.index).flashing(
"error" -> "Missing file"
)
}
}
}
The above code is a simple file upload controller in Play Framework. It takes a file from a multipart form data request and saves it to a temporary directory without deleting any previous files and without any limit on the number of files that can be uploaded.
Here is the breakdown of the code:
-
import play.api.mvc._
and
import play.api.libs.Files
are importing necessary Play libraries for handling HTTP requests and file operations respectively.
- class FileController @Inject() (mcc: MessagesControllerComponents) extends MessagesAbstractController(mcc)
is defining a new controller that extends Play's
MessagesAbstractController
.
- def upload = Action(parse.multipartFormData) { request => ... }
is defining an action that handles file upload requests. It parses the request as multipart form data.
- request.body.file("picture").map { picture => ... }
is extracting the file from the request. If a file is found, it is processed by the code in the curly braces.
- picture.ref.moveTo(Paths.get(s"/tmp/picture/$filename"), replace = false)
is moving the uploaded file to a temporary directory. The
replace = false
parameter means that if a file with the same name already exists, the new file will not replace it.
- Ok("File uploaded")
is sending a success response to the client if the file has been uploaded successfully.
- .getOrElse { ... }
is handling the case where no file is found in the request. It redirects the user to the index page with an error message.
The vulnerability in this code is that it does not limit the number of files a user can upload. This can lead to a Denial of Service (DoS) attack if a malicious user decides to upload a large number of files to overwhelm the server's storage capacity. Additionally, the
replace = false
parameter can lead to confusion and potential data loss if different files with the same name are uploaded.
import play.api.mvc._
import play.api.libs.Files
import java.nio.file.{Files => JFiles, Paths}
import scala.concurrent.ExecutionContext
class FileController @Inject() (mcc: MessagesControllerComponents)(implicit ec: ExecutionContext) extends MessagesAbstractController(mcc) {
private val MAX_UPLOADS_PER_HOUR = 10
private val uploadCounts: scala.collection.mutable.Map[String, Int] = scala.collection.mutable.Map()
def upload = Action(parse.maxLength(1024 * 1024, parse.multipartFormData)) { request =>
request.body match {
case Left(MaxSizeExceeded(_)) =>
Redirect(routes.HomeController.index).flashing(
"error" -> "File size limit exceeded"
)
case Right(multipartForm) =>
multipartForm.file("picture").map { picture =>
val filename = picture.filename
val contentType = picture.contentType
val user = request.session.get("user").getOrElse("unknown")
if (uploadCounts.getOrElse(user, 0) >= MAX_UPLOADS_PER_HOUR) {
Redirect(routes.HomeController.index).flashing(
"error" -> "Upload limit exceeded"
)
} else {
val path = Paths.get(s"/secure/location/$filename")
if (JFiles.exists(path)) {
JFiles.delete(path)
}
picture.ref.moveTo(path, replace = true)
uploadCounts.update(user, uploadCounts.getOrElse(user, 0) + 1)
Ok("File uploaded")
}
}.getOrElse {
Redirect(routes.HomeController.index).flashing(
"error" -> "Missing file"
)
}
}
}
}
The updated code now includes several security measures to prevent insecure file upload:
1. File Size Limit: The
parse.maxLength
function is used to limit the size of the uploaded file to 1MB. If the file size exceeds this limit, the user is redirected to the home page with an error message.
2. Upload Limit: A mutable map
uploadCounts
is used to track the number of files uploaded by each user per hour. If a user tries to upload more than
MAX_UPLOADS_PER_HOUR
files, they are redirected to the home page with an error message.
3. File Replacement: If a file with the same name already exists in the server, it is deleted before the new file is saved. This is done using the
java.nio.file.Files.delete
method.
4. Secure File Storage: The uploaded files are saved in a secure location (
/secure/location/
). This location should have restricted access permissions to prevent unauthorized access.
5. Error Handling: Proper error handling is implemented for file upload failures. If the file is missing or the file size/upload limit is exceeded, the user is redirected to the home page with an appropriate error message.