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
Insecure Code Example
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.
Secure Code Example
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
Last updated
2023/09/18