Insecure or unset HTTP headers - CORS - Java

Insecure or unset HTTP headers - CORS - Java

Need

Secure and properly configure HTTP headers to prevent cross-origin resource sharing (CORS) vulnerabilities.

Context

  • Usage of Java for building cross-platform applications
  • Usage of javax.servlet for building Java web applications with Servlet technology
  • Usage of javax.servlet.http for handling HTTP requests and responses in Java web applications

Description

Non compliant code

        import javax.servlet.*;
import javax.servlet.http.*;

public class VulnerableServlet extends HttpServlet {
    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.addHeader("Access-Control-Allow-Origin", "*");
        resp.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS, HEAD");
        resp.addHeader("Access-Control-Allow-Headers", "Content-Type");
        resp.addHeader("Access-Control-Max-Age", "86400");
    }
}
        
        

This code is a simple Java servlet that handles HTTP OPTIONS requests, which are used in CORS (Cross-Origin Resource Sharing) preflight checks.

The vulnerability lies in the Access-Control-Allow-Origin header. This header is used by the browser to determine if a web page from a different domain can perform a CORS request to the server. In the code above, the server responds with Access-Control-Allow-Origin: *, which means that any domain is allowed to perform CORS requests. This is a security risk because it allows any website to interact with your server, which could potentially lead to data leaks or other security issues.

Similarly, the Access-Control-Allow-Methods header is set to allow all HTTP methods, and the Access-Control-Allow-Headers header is set to allow the Content-Type header. This means that any CORS request, regardless of the HTTP method or the headers used, will be allowed. This could potentially allow an attacker to perform actions that should not be allowed from a different domain.

The Access-Control-Max-Age header is set to a large value, which means that the browser will cache the preflight response for a long time. This could potentially increase the impact of an attack, as the browser will not perform a new preflight request for a long time, even if the server's CORS policy changes.

Steps

  • Remove the wildcard (*) from the 'Access-Control-Allow-Origin' header.
  • Specify the allowed origins explicitly in the 'Access-Control-Allow-Origin' header.
  • Validate the 'Origin' header in the request to ensure it matches the allowed origins.
  • Remove unnecessary HTTP methods from the 'Access-Control-Allow-Methods' header.
  • Validate the requested method in the 'Access-Control-Request-Method' header against the allowed methods.
  • Remove unnecessary headers from the 'Access-Control-Allow-Headers' header.
  • Validate the requested headers in the 'Access-Control-Request-Headers' header against the allowed headers.
  • Set a reasonable value for the 'Access-Control-Max-Age' header to limit the preflight request caching time.

Compliant code

        import javax.servlet.*;
import javax.servlet.http.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class SecureServlet extends HttpServlet {
    private static final Set ALLOWED_ORIGINS = Collections.unmodifiableSet(
            new HashSet<>(Arrays.asList("http://example1.com", "http://example2.com")));
    private static final Set ALLOWED_METHODS = Collections.unmodifiableSet(
            new HashSet<>(Arrays.asList("GET", "POST", "OPTIONS")));
    private static final Set ALLOWED_HEADERS = Collections.unmodifiableSet(
            new HashSet<>(Arrays.asList("Content-Type")));

    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String origin = req.getHeader("Origin");
        if (ALLOWED_ORIGINS.contains(origin)) {
            resp.addHeader("Access-Control-Allow-Origin", origin);
        }

        String method = req.getHeader("Access-Control-Request-Method");
        if (ALLOWED_METHODS.contains(method)) {
            resp.addHeader("Access-Control-Allow-Methods", method);
        }

        String headers = req.getHeader("Access-Control-Request-Headers");
        if (headers != null) {
            String[] requestedHeaders = headers.split(",");
            for (String requestedHeader : requestedHeaders) {
                if (ALLOWED_HEADERS.contains(requestedHeader.trim())) {
                    resp.addHeader("Access-Control-Allow-Headers", requestedHeader);
                }
            }
        }

        resp.addHeader("Access-Control-Max-Age", "3600");
    }
}
        
        

The above code fixes the vulnerability by:

1. Removing the wildcard (*) from the 'Access-Control-Allow-Origin' header and specifying the allowed origins explicitly. The allowed origins are stored in a Set for easy lookup.

2. Validating the 'Origin' header in the request to ensure it matches the allowed origins. If the origin is allowed, it is added to the 'Access-Control-Allow-Origin' header.

3. Removing unnecessary HTTP methods from the 'Access-Control-Allow-Methods' header and validating the requested method in the 'Access-Control-Request-Method' header against the allowed methods. If the method is allowed, it is added to the 'Access-Control-Allow-Methods' header.

4. Removing unnecessary headers from the 'Access-Control-Allow-Headers' header and validating the requested headers in the 'Access-Control-Request-Headers' header against the allowed headers. If a header is allowed, it is added to the 'Access-Control-Allow-Headers' header.

5. Setting a reasonable value for the 'Access-Control-Max-Age' header to limit the preflight request caching time. In this case, the caching time is set to 1 hour (3600 seconds).

References