Asymmetric denial of service - Content length - Java

Asymmetric denial of service - Content length - Java

Need

Prevention of asymmetric denial of service attacks through proper validation and enforcement of Content-Length field in requests.

Context

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

Description

Non compliant code

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

public class VulnerableServlet extends HttpServlet {
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        int contentLength = request.getContentLength();
        ServletInputStream in = request.getInputStream();
        byte[] requestBytes = new byte[contentLength];
        in.read(requestBytes);
        String requestData = new String(requestBytes, "UTF-8");
        // process the request
    }
}
        
        

The above Java servlet code is vulnerable to Asymmetric Denial of Service (DoS) attacks due to the misuse of the Content-Length header in HTTP requests.

In the doPost method, the getContentLength function is used to retrieve the value of the Content-Length header from the HTTP request. This value is then used to create a byte array ( requestBytes) of the same size. The read function is then used to read the content of the request into the byte array.

The vulnerability arises because the server blindly trusts the Content-Length header value provided by the client. An attacker can exploit this by providing a very large value for the Content-Length header, causing the server to allocate a large amount of memory for the requestBytes array. This can lead to excessive memory usage, potentially crashing the server or significantly degrading its performance.

Furthermore, the attacker can keep the connection open by sending small amounts of data before the connection termination timer expires, causing the server to wait for more data. This can further exacerbate the impact of the attack by consuming server resources for an extended period of time.

Steps

  • Validate the Content-Length field: Check if the value of the Content-Length field is within a reasonable range. Reject requests with excessively large values.
  • Implement rate limiting: Limit the number of requests that can be processed within a certain time frame. This can help prevent an attacker from overwhelming the server with a large number of requests.
  • Implement connection timeout: Set a timeout for the connection to terminate if no further data is received within a specified time. This can prevent the connection from being kept dangerously active.
  • Implement request size limit: Set a maximum limit for the size of the request payload that the server can handle. Reject requests that exceed this limit.
  • Use streaming instead of reading the entire request into memory: Instead of reading the entire request payload into memory, process the request in a streaming manner. This can help reduce memory usage and mitigate the impact of large requests.
  • Implement request validation: Validate the request data to ensure it is in the expected format and does not contain any malicious content. This can help prevent various types of attacks, including denial of service.

Compliant code

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

public class SecureServlet extends HttpServlet {
    private static final int MAX_CONTENT_LENGTH = 1024 * 1024; // 1MB
    private static final int TIMEOUT = 5000; // 5 seconds

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        int contentLength = request.getContentLength();

        // Validate the Content-Length field
        if (contentLength > MAX_CONTENT_LENGTH) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request size exceeds limit");
            return;
        }

        // Set connection timeout
        request.setReadTimeout(TIMEOUT);

        // Use streaming instead of reading the entire request into memory
        ServletInputStream in = request.getInputStream();
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        int nRead;
        byte[] data = new byte[1024];
        while ((nRead = in.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }

        buffer.flush();
        byte[] requestBytes = buffer.toByteArray();

        String requestData = new String(requestBytes, "UTF-8");
        // process the request
    }
}
        
        

The updated code includes several security measures to mitigate the risk of an asymmetric denial of service attack:

1. Content-Length Validation: The code checks if the value of the Content-Length field is within a reasonable range (1MB in this case). If the request size exceeds this limit, the server responds with a 400 Bad Request error and terminates the connection.

2. Connection Timeout: The code sets a timeout for the connection using the setReadTimeout method. If no further data is received within the specified time (5 seconds in this case), the connection is terminated.

3. Streaming: Instead of reading the entire request payload into memory, the code processes the request in a streaming manner. This reduces memory usage and mitigates the impact of large requests.

Note: The other measures mentioned in the steps (rate limiting and request validation) are not included in this code snippet as they are typically implemented at a higher level (e.g., in a web application firewall or a reverse proxy).

References