Asymmetric denial of service - Content length
Need
Prevention of Content-Length spoofing and resource exhaustion
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
Description
Non compliant code
import play.api.mvc._
object ApplicationController extends Controller {
def handleRequest = Action { request =>
request.headers.get("Content-Length") match {
case Some(contentLength) =>
val data = request.body.asRaw.flatMap(_.asBytes(contentLength.toInt))
Ok("Data received")
case None => BadRequest("Content-Length not specified")
}
}
}
The above Scala code is using the Play framework to handle incoming HTTP requests. The handleRequest
function is vulnerable to an Asymmetric Denial of Service attack due to its handling of the Content-Length
header.
The function retrieves the Content-Length
header from the request and attempts to read the body of the request according to the specified length. If an attacker specifies a very large value in the Content-Length
header, the server will expect to receive a large amount of data and keep the connection open until it receives all the data, or until the connection termination timer expires.
By sending a small piece of data after a validly terminated header, and then waiting until just before the connection termination timer expires before sending another small piece of data, an attacker can keep the connection open and active, thereby potentially causing a Denial of Service.
The vulnerability lies in the line val data = request.body.asRaw.flatMap(_.asBytes(contentLength.toInt))
. This line attempts to read the entire body of the request according to the specified Content-Length
, without any checks for unreasonable or excessive values.
Steps
- Validate the Content-Length header value to ensure it is within a reasonable range.
- Implement rate limiting or request throttling mechanisms to prevent excessive requests with high Content-Length values.
- Implement timeout mechanisms to terminate idle connections after a certain period of time.
- Consider using streaming or chunked transfer encoding to handle large amounts of data without relying solely on the Content-Length header.
- Implement request validation to ensure that the request body matches the expected content length specified in the Content-Length header.
- Regularly update and patch the Play framework and its dependencies to benefit from security fixes and improvements.
- Implement logging and monitoring to detect and investigate any suspicious or malicious activities related to Content-Length values.
Compliant code
import play.api.mvc._
import scala.concurrent.duration._
object ApplicationController extends Controller {
val MAX_CONTENT_LENGTH = 100000 // Define a reasonable maximum content length
val IDLE_TIMEOUT = 5.minutes // Define a reasonable idle timeout
def handleRequest = Action(parse.maxLength(MAX_CONTENT_LENGTH, parse.anyContent)) { request =>
request.headers.get("Content-Length") match {
case Some(contentLength) =>
if (contentLength.toInt > MAX_CONTENT_LENGTH) {
BadRequest("Content-Length too large")
} else {
val data = request.body.asRaw.flatMap(_.asBytes(contentLength.toInt))
Ok("Data received")
}
case None => BadRequest("Content-Length not specified")
}
}.withTimeout(IDLE_TIMEOUT)
}
The updated code includes several measures to mitigate the risk of an asymmetric denial of service attack via the Content-Length header.
Content-Length Validation: The
MAX_CONTENT_LENGTH
constant is defined to specify a reasonable maximum content length. If the Content-Length header value exceeds this maximum, a BadRequest response is returned.Request Throttling: The
parse.maxLength
method is used to limit the size of the request body to the maximum content length. If the actual size of the request body exceeds this limit, a BadRequest response is returned.Idle Timeout: The
withTimeout
method is used to terminate idle connections after a certain period of time. TheIDLE_TIMEOUT
constant is defined to specify a reasonable idle timeout.Request Validation: The
request.body.asRaw.flatMap(_.asBytes(contentLength.toInt))
line of code validates that the request body matches the expected content length specified in the Content-Length header.Logging and Monitoring: Although not explicitly shown in the code, it is recommended to implement logging and monitoring to detect and investigate any suspicious or malicious activities related to Content-Length values.
Regular Updates and Patches: Although not explicitly shown in the code, it is recommended to regularly update and patch the Play framework and its dependencies to benefit from security fixes and improvements.