Symmetric denial of service
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:
-
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. -
Caching: The
Cache::has
andCache::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. TheCache::put
method is used to cache the result of a heavy processing task. -
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. -
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.