Prevention of service outages caused by repetitive resource-intensive requests
import play.api.mvc._
object VulnerableController extends Controller {
def vulnerableAction = Action { request =>
val expensiveOperation = request.body.asJson.get("data").as[String]
// An expensive operation that consumes a lot of resources or takes too long to be processed
Thread.sleep(10000) // Simulating an expensive operation
Ok("Done")
}
}
This code illustrates a controller action in Play framework that is prone to a symmetric Denial of Service (DoS) attack. The
vulnerableAction
method takes in a request and performs an expensive operation that consumes a lot of resources or takes a considerable amount of time to process.
The
Thread.sleep(10000)
line is used to simulate this expensive operation. It causes the current thread to suspend execution for a specified period. This is just a placeholder for a potentially resource-intensive operation like complex calculations, large database queries, etc.
The problem here is that if an attacker sends multiple requests to this endpoint in quick succession, it can lead to the server becoming unresponsive. This is because each request will cause the server to pause for a significant amount of time (10 seconds in this case), which can quickly exhaust the server's resources if many such requests are made concurrently.
This is a typical example of a symmetric DoS vulnerability, where an attacker can cause a denial of service by repeatedly triggering a resource-intensive operation.
import play.api.mvc._
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
object FixedController extends Controller {
implicit val ec: ExecutionContext = ExecutionContext.global
// Implement rate limiting
val rateLimiter = new RateLimiter(5, 1.minute) // Allow 5 requests per minute
def fixedAction = Action.async { request =>
rateLimiter.attempt() match {
case true => // If the rate limit has not been exceeded
val expensiveOperation = request.body.asJson.get("data").as[String]
// Use asynchronous processing and implement a timeout
val futureResult = Future {
Thread.sleep(10000) // Simulating an expensive operation
"Done"
}
futureResult.map(result => Ok(result)).recover {
case _: java.util.concurrent.TimeoutException => RequestTimeout("Request took too long.")
}
case false => // If the rate limit has been exceeded
Future.successful(TooManyRequest("Too many requests. Please try again later."))
}
}
}
The fixed code introduces several measures to mitigate the Symmetric Denial of Service vulnerability:
1. Rate Limiting: A
RateLimiter
class is used to limit the number of requests that can be made within a certain time frame. In this case, the rate limit is set to 5 requests per minute. If a client exceeds this limit, a
TooManyRequest
response is returned.
2. Asynchronous Processing: The
Action.async
method is used instead of
Action
to process the request asynchronously. This means that the server thread is not blocked while waiting for the expensive operation to complete.
3. Timeouts: A timeout is implemented using the
recover
method on the
Future
returned by the expensive operation. If the operation takes longer than the specified timeout, a
RequestTimeout
response is returned.
4. Offloading Expensive Operations: Although not shown in the code, consider offloading the expensive operation to a separate service or background task to avoid impacting the responsiveness of the server. This could be done using a message queue or a separate microservice, for example.
5. Optimizing Expensive Operations: Also not shown in the code, but consider optimizing the expensive operation to reduce resource consumption and processing time. This could involve optimizing database queries, using caching, or reducing the complexity of the operation.