Symmetric denial of service - PHP

Symmetric denial of service - PHP

Need

Prevention of service outages caused by repeated resource-intensive requests

Context

  • Usage of PHP 7.0 for server-side scripting and web development
  • Usage of Request for making HTTP requests
  • Usage of response for handling HTTP responses in a Node.js application
  • Usage of JSON for data interchange and storage

Description

Non compliant code

        public function heavyProcessing(Request $request)
{
    $data = $request->all();

    // A heavy processing task that consumes a lot of resources
    for ($i = 0; $i < 1000000; $i++) {
        $data[] = $i;
    }

    return response()->json($data);
}
        
        

The above code represents a function in a Laravel application that performs a heavy processing task. This function is triggered by a HTTP request and it consumes a lot of resources.

The heavy processing task is a loop that iterates one million times, which is a significant load for the server. Each iteration adds a new element to the $data array, which is eventually returned as a JSON response.

This function is vulnerable to a Symmetric Denial of Service (DoS) attack. An attacker can successively repeat a request to this function, causing the server to consume a large amount of resources for each request. If the server receives too many such requests, it may become unresponsive due to the heavy load, resulting in a denial of service for other users.

Steps

  • Limit the number of requests a single client can make in a certain time frame. This can be done using rate limiting.
  • Optimize the heavy processing task. If possible, reduce the complexity of the task or use more efficient algorithms.
  • Offload the heavy processing task to a queue to be processed in the background. This way, the server can respond quickly to the request and process the task when resources are available.
  • Implement a timeout for processing requests. If a request takes too long to process, the server can stop processing it and return an error message to the client.
  • Use caching to store the results of heavy processing tasks. If the same request is made multiple times, the server can return the cached result instead of processing the request again.

Compliant code

        use Illuminate\\Support\\Facades\\Redis;
use Illuminate\\Support\\Facades\\Cache;

public function heavyProcessing(Request $request)
{
    $ip = $request->ip();
    $key = 'rate_limit:'.$ip;
    $maxAttempts = 100; // maximum number of attempts in a certain time frame
    $decayMinutes = 1; // time frame in minutes

    if (Redis::get($key) >= $maxAttempts) {
        return response()->json(['message' => 'Too many attempts. Please slow down.'], 429);
    }

    Redis::throttle($key)->allow($maxAttempts)->every($decayMinutes)->then(function () use ($request) {
        $data = $request->all();

        // Check if the result is cached
        if (Cache::has($request)) {
            return response()->json(Cache::get($request));
        }

        // A heavy processing task that consumes a lot of resources
        $data = $this->processData($data);

        // Cache the result
        Cache::put($request, $data, 60);

        return response()->json($data);
    }, function () {
        return response()->json(['message' => 'Too many attempts. Please slow down.'], 429);
    });
}

private function processData($data)
{
    $result = [];

    // A heavy processing task that consumes a lot of resources
    for ($i = 0; $i < 1000000; $i++) {
        $result[] = $i;
    }

    return $result;
}
        
        

The code above implements several measures to prevent a symmetric denial of service attack:

1. Rate Limiting: The Redis::throttle method is used to limit the number of requests a single client can make in a certain time frame. If a client makes too many requests, a 429 (Too Many Requests) response is returned.

2. Caching: The Cache::has and Cache::get methods are used to check if the result of a heavy processing task is cached. If it is, the cached result is returned instead of processing the request again. The Cache::put method is used to cache the result of a heavy processing task.

3. Offloading Heavy Processing: The heavy processing task is moved to a separate processData method. This method could be further modified to offload the processing to a queue.

4. Timeouts: Although not explicitly shown in the code, timeouts could be implemented in the processData method to stop processing if it takes too long. This would involve using a timer and checking the elapsed time at regular intervals during the processing. If the elapsed time exceeds a certain limit, the method could throw an exception to stop processing.

By implementing these measures, the server can protect itself from being rendered unresponsive by repeated requests that consume a lot of resources or take too long to process.

References