Skip to main content

Improper control of interaction frequency

Need

Enforce rate limiting to control the frequency of user interactions

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 play.api.libs.json for JSON parsing and manipulation in Play Framework

Description

Non compliant code

import play.api.mvc._
import play.api.libs.json._

class Application @Inject()(val controllerComponents: ControllerComponents) extends BaseController {

def postMessage = Action(parse.json) { request =>
val message = (request.body \\ "message").as[String]
// Processing the message
Ok("Message received.")
}
}

In the above Scala code, we have an endpoint postMessage in the Application controller. This endpoint accepts JSON requests and extracts a message string from the request body.

The vulnerability here is that there is no limitation or control over the frequency of requests that a user can send to this endpoint. This means an attacker could potentially send a large number of requests in a short period of time, causing a Denial of Service (DoS) attack by overloading the server.

This could also lead to other potential issues like resource exhaustion, where the server might run out of resources while trying to process a large number of requests. This could slow down the server significantly, affecting all users and not just the one sending the large number of requests.

In addition, if the processing of each message is resource-intensive or involves writing to a database, this could lead to further issues like database saturation or excessive disk usage.

This vulnerability can be mitigated by implementing a rate limiting mechanism, which would limit the number of requests a user can send to this endpoint within a certain period of time.

Steps

  • Implement rate limiting to restrict the number of requests a user can make within a certain time frame.
  • Use a middleware or filter to intercept incoming requests and check if the user has exceeded the allowed request limit.
  • Track the number of requests made by each user and the timestamp of the last request.
  • Set a maximum limit for the number of requests a user can make within a specific time period.
  • If the user exceeds the limit, return an appropriate error response (e.g., 429 Too Many Requests).
  • Consider using a distributed cache or database to store the request count and timestamp for each user.
  • Configure the rate limit values (e.g., maximum number of requests and time period) based on your application's requirements and expected traffic.
  • Test the rate limiting functionality to ensure it is working as expected.

Compliant code

import play.api.mvc._
import play.api.libs.json._
import scala.concurrent.ExecutionContext
import akka.actor.ActorSystem
import scala.concurrent.duration._
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import play.api.cache.AsyncCacheApi

class RateLimiter @Inject()(cache: AsyncCacheApi)(implicit system: ActorSystem, ec: ExecutionContext) {
def rateLimit(userId: String, limit: Int, within: FiniteDuration)(f: => Result): Result = {
val key = s"rateLimit:$userId"
val result = cache.get[Int](key).map {
case Some(count) if count >= limit =>
Results.TooManyRequests("You have exceeded your rate limit.")
case _ =>
cache.set(key, 1, within)
f
}
Await.result(result, Duration(5, TimeUnit.SECONDS))
}
}

class Application @Inject()(val controllerComponents: ControllerComponents, rateLimiter: RateLimiter) extends BaseController {

def postMessage = Action(parse.json) { request =>
val userId = request.headers.get("userId").getOrElse("anonymous")
rateLimiter.rateLimit(userId, 100, 1.hour) {
val message = (request.body \\ "message").as[String]
// Processing the message
Ok("Message received.")
}
}
}

The code above introduces a RateLimiter class that uses Play's AsyncCacheApi to track the number of requests made by each user. The rateLimit method takes a userId, a limit (the maximum number of requests allowed within a certain time period), and a within parameter (the time period), and a function f that returns a Result.

The rateLimit method first checks if the user has already made limit requests within the within time period. If they have, it returns a TooManyRequests result. If they haven't, it increments the user's request count in the cache and sets the cache to expire after the within time period, then calls the function f.

In the Application controller, the postMessage action now uses the RateLimiter to limit the number of requests a user can make to 100 per hour. The userId is taken from the request's headers, and if it's not present, the user is treated as "anonymous".

This solution effectively limits the rate at which users can post messages to the server, mitigating the risk of denial-of-service attacks and server resource exhaustion.

References