Asymmetric denial of service - Java

Asymmetric denial of service - Java

Need

The need is to implement measures that prevent or mitigate asymmetric denial of service attacks, ensuring the server remains responsive and available to legitimate users.

Context

  • Usage of Java 8 for developing applications with enhanced features and performance
  • Usage of javax.servlet-api for developing Java web applications with servlets

Description

Non compliant code

        import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;

public class VulnerableServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String param = request.getParameter("param");
        if (param != null) {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                response.getWriter().write("Response " + i + "\\n");
            }
        }
    }
}
        
        

The above servlet VulnerableServlet is vulnerable to an asymmetric denial of service attack. It's a simple HTTP servlet that listens for GET requests. When it receives a request, it checks if the request contains a parameter named "param". If it does, it enters an infinite loop that writes a large number of responses back to the client.

The vulnerability lies in the loop. By setting the loop to iterate Integer.MAX_VALUE times, it's possible to send a single request that will cause the server to generate an enormous number of responses. This is an example of an amplification attack, where a single request can consume a large amount of server resources.

Additionally, if a malicious request is sent with a very large "param" value, it can cause the server to consume all of its available memory, causing it to become unresponsive. This is a type of denial of service attack.

This code is vulnerable because it does not limit the number of responses it can generate from a single request, and it does not validate or limit the size of the "param" value. This allows an attacker to send a single request that can consume all of the server's resources, causing a denial of service.

Steps

  • Implement rate limiting to prevent amplification attacks
  • Validate and sanitize user input to prevent malicious requests
  • Implement resource limits to prevent excessive resource consumption

Compliant code

        import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang3.StringUtils;

public class SecureServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    private static final int MAX_REQUESTS_PER_SECOND = 5;
    private AtomicInteger requestCount = new AtomicInteger(0);

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if (requestCount.incrementAndGet() > MAX_REQUESTS_PER_SECOND) {
            response.sendError(HttpServletResponse.SC_TOO_MANY_REQUESTS, "Too many requests");
            return;
        }

        String param = request.getParameter("param");
        if (StringUtils.isNumeric(param)) {
            int limit = Integer.parseInt(param);
            if (limit > 1000) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request limit exceeded");
                return;
            }

            for (int i = 0; i < limit; i++) {
                response.getWriter().write("Response " + i + "\\n");
            }
        } else {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid request");
        }

        requestCount.decrementAndGet();
    }

    @Override
    public void init() throws ServletException {
        super.init();
        new Thread(() -> {
            while (true) {
                requestCount.set(0);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
        
        

The above code is a secure version of the original servlet. It implements rate limiting, input validation, and resource limits to prevent asymmetric denial of service attacks.

1. Rate Limiting: The requestCount variable is used to count the number of requests received per second. If the count exceeds MAX_REQUESTS_PER_SECOND, the server responds with a 429 (Too Many Requests) error. The count is reset every second by a background thread started in the init method.

2. Input Validation: The param request parameter is validated using the StringUtils.isNumeric method from Apache Commons Lang. If the parameter is not a valid number, the server responds with a 400 (Bad Request) error.

3. Resource Limits: The param request parameter is also used to limit the number of responses generated by the server. If the parameter value exceeds 1000, the server responds with a 400 (Bad Request) error. This prevents a single request from consuming an excessive amount of server resources.

By implementing these measures, the server can effectively mitigate the risk of asymmetric denial of service attacks.

References