Prevention of Content-Length spoofing and resource exhaustion
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.
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.