Symmetric denial of service - Java

Symmetric denial of service - Java

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 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:

1. 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 a 503 Service Unavailable status.

2. 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.

3. Timeouts: The Future.get method is used with a timeout of 5 seconds. If the operation does not complete within this time, a TimeoutException is thrown and the operation is cancelled. The server responds with a 500 Internal Server Error status.

4. 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.

References