Skip to main content

Asymmetric denial of service

Need

The solution to the vulnerability of asymmetric denial of service is the need for robust resource management and mitigation techniques to prevent service outages caused by malicious requests or resource exhaustion.

Context

  • Usage of Scala for building scalable and functional applications
  • Usage of play.api.mvc._ for handling HTTP requests and responses in Play Framework
  • Usage of javax.inject for dependency injection in Java applications
  • Usage of Scala's concurrent package for managing execution context and working with asynchronous futures

Description

Non compliant code

import play.api.mvc._
import javax.inject._
import scala.concurrent.{ExecutionContext, Future}

@Singleton
class HomeController @Inject()(cc: ControllerComponents)(implicit ec: ExecutionContext) extends AbstractController(cc) {

def index() = Action.async { implicit request: Request[AnyContent] =>
val input = request.body.asText.getOrElse("")
val result = Future {
// Simulate expensive computation
Thread.sleep(10000)
input.reverse
}
result.map(r => Ok(r))
}
}

This code defines a simple HTTP server using the Play Framework in Scala. The server has one endpoint (index) that accepts any request, extracts the body as a text, and reverses it after a simulated expensive computation (a delay of 10 seconds).

The vulnerability lies in the lack of input validation and rate limiting. In this case, an attacker can send a large number of requests or large payloads to the server, causing it to become unresponsive due to resource exhaustion. This is because the server will try to process each request, including the simulated expensive computation, without any limits or controls.

An amplification attack can also be carried out by sending a single request with a large payload. The server will attempt to reverse the large string, consuming a large amount of resources and potentially causing a denial of service.

To mitigate this vulnerability, you should implement input validation to control the size of the payloads that your server will accept. Additionally, you should implement rate limiting to control the number of requests that a single client can send in a given time period.

Steps

  • Implement rate limiting to prevent amplification attacks.
  • Validate and sanitize user input to prevent malicious requests.
  • Optimize the expensive computation to reduce resource consumption.
  • Consider using asynchronous non-blocking operations to improve server responsiveness.

Compliant code

import play.api.mvc._
import javax.inject._
import scala.concurrent.{ExecutionContext, Future}
import akka.pattern.CircuitBreaker
import akka.pattern.pipe
import scala.concurrent.duration._

@Singleton
class HomeController @Inject()(cc: ControllerComponents, breaker: CircuitBreaker)(implicit ec: ExecutionContext) extends AbstractController(cc) {

// Rate limiter
val rateLimiter = new RateLimiter(1000, 1.minute) // Allow 1000 requests per minute

def index() = Action.async { implicit request: Request[AnyContent] =>
if (rateLimiter.allow()) {
val input = request.body.asText.getOrElse("")
// Validate and sanitize input
val sanitizedInput = sanitize(input)
val result = breaker.withCircuitBreaker {
Future {
// Optimize expensive computation
optimizedComputation(sanitizedInput)
}
}
result.map(r => Ok(r)).recover {
case e: Exception => InternalServerError("Error processing request")
}
} else {
Future.successful(TooManyRequests("Too many requests"))
}
}

def sanitize(input: String): String = {
// Implement input sanitization
}

def optimizedComputation(input: String): String = {
// Implement optimized computation
input.reverse
}
}

The updated code includes several measures to mitigate the risk of an asymmetric denial of service attack:

  1. Rate Limiting: A rate limiter is implemented to limit the number of requests a client can make in a certain time period. In this case, the rate limiter allows 1000 requests per minute. If a client exceeds this limit, a TooManyRequests response is returned.

  2. Input Validation and Sanitization: The sanitize function is used to validate and sanitize user input. This can help prevent malicious requests that could break the application or consume an enormous amount of resources.

  3. Optimized Computation: The optimizedComputation function is used to perform the expensive computation in a more efficient manner. This can help reduce resource consumption.

  4. Asynchronous Non-Blocking Operations: The CircuitBreaker pattern is used to prevent a failure in one part of the system from cascading to other parts. It does this by 'breaking' the connection to the failing part for a while, allowing it to recover. This can improve server responsiveness.

  5. Error Handling: The recover function is used to handle any exceptions that occur during the processing of the request. If an exception occurs, an InternalServerError response is returned. This can help ensure that the server remains responsive even if an error occurs.

References