Asymmetric denial of service - Content length - Scala

Asymmetric denial of service - Content length - Scala

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.

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

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

3. Idle Timeout: The withTimeout method is used to terminate idle connections after a certain period of time. The IDLE_TIMEOUT constant is defined to specify a reasonable idle timeout.

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

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

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

References