Symmetric denial of service
Need
Prevention of service outages caused by repeated resource-intensive requests
Context
- Usage of Java 8 for developing applications with enhanced features and performance
- Usage of javax.servlet-api for building Java web applications with servlets
Description
Non compliant code
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class VulnerableServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
while(true) {
// A resource-intensive operation
calculateFibonacci(1000000);
}
}
private int calculateFibonacci(int n) {
if (n <= 1) return n;
else return calculateFibonacci(n-1) + calculateFibonacci(n-2);
}
}
The above code represents a servlet in Java which is vulnerable to a symmetric denial of service attack. The servlet is defined as VulnerableServlet
and it extends HttpServlet
class provided by javax.servlet.http
package.
In the servlet, there's a doGet
method which is called on every GET request. Inside this method, there's an infinite loop where a resource-intensive operation is performed. This operation is the calculation of a large Fibonacci number (1,000,000th Fibonacci number) which is a very expensive operation in terms of CPU and memory usage.
The calculateFibonacci
method is a recursive function which calculates the Fibonacci number of a given integer. This function has an exponential time complexity due to the repeated calculations of the same Fibonacci numbers.
This code is vulnerable because an attacker can repeatedly send GET requests to the server, causing the server to continuously calculate large Fibonacci numbers. Since this operation is very resource-intensive, the server will eventually run out of resources and become unresponsive, resulting in a denial of service.
Steps
- Implement rate limiting to restrict the number of requests a client can make within a certain time period.
- Optimize the resource-intensive operation to reduce the amount of resources it consumes.
- Implement timeouts for requests to prevent them from taking too long to be processed.
- Consider using asynchronous processing or background tasks for long-running operations to avoid blocking the server.
- Monitor server performance and resource usage to identify and address any bottlenecks or inefficiencies.
- Implement proper error handling and response codes to gracefully handle excessive requests.
Compliant code
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.*;
public class FixedServlet extends HttpServlet {
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
private final Semaphore semaphore = new Semaphore(5); // Limit to 5 requests at a time
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
if (!semaphore.tryAcquire()) {
response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return;
}
Future<Integer> future = executorService.submit(() -> calculateFibonacci(1000000));
try {
future.get(5, TimeUnit.SECONDS); // Timeout after 5 seconds
} catch (InterruptedException | ExecutionException | TimeoutException e) {
future.cancel(true);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
semaphore.release();
}
}
private int calculateFibonacci(int n) {
if (n <= 1) return n;
else return calculateFibonacci(n-1) + calculateFibonacci(n-2);
}
}
The fixed code implements several strategies to mitigate the symmetric denial of service vulnerability:
-
Rate Limiting: A
Semaphore
is used to limit the number of concurrent requests that can be processed by the servlet. If the semaphore cannot acquire a permit, it means the rate limit has been reached and the server responds with a503 Service Unavailable
status. -
Asynchronous Processing: The resource-intensive operation is submitted to an
ExecutorService
for asynchronous processing. This allows the server to continue handling other requests without being blocked by the long-running operation. -
Timeouts: The
Future.get
method is used with a timeout of 5 seconds. If the operation does not complete within this time, aTimeoutException
is thrown and the operation is cancelled. The server responds with a500 Internal Server Error
status. -
Error Handling: Proper error handling is implemented to gracefully handle exceptions and respond with appropriate HTTP status codes.
Note: The calculateFibonacci
method is still very inefficient and could be optimized, for example, by using dynamic programming or matrix exponentiation. However, this is beyond the scope of this fix.