Unrestricted Resource Consumption

How to Fix Unrestricted Resource Consumption in Spring Boot [March 2026] [CVE-2023-20883]

[Updated March 2026] Updated CVE-2023-20883

Overview

CVE-2023-20883 describes an unrestricted resource consumption vulnerability in Spring Boot apps that rely on Spring MVC when used behind a reverse proxy cache. In affected versions (3.0.0-3.0.6, 2.7.0-2.7.11, 2.6.0-2.6.14, 2.5.0-2.5.14 and older unsupported releases), crafted requests can cause DoS by exhausting CPU and memory resources due to how request parameters and caching interplay with the proxy. This can lead to thread pool saturation and degraded service for legitimate users when an attacker floods the backend through the proxy cache chain. The issue maps to CWE-400 (Uncontrolled Resource Consumption) and is triggered by scenarios where Spring MVC processes requests with many parameters or heavy payloads in a way that is not bounded by server or application logic when proxied behind a cache. This vulnerability is a classic DoS vector that attackers can exploit without needing to breach authentication or authorization on the application itself. The remediation is to upgrade to a patched Spring Boot version and to implement defense-in-depth with input size/parameter limiting and proactive request-rate controls. This CVE is specifically called out for Spring Boot environments that incorporate Spring MVC with reverse proxy caching layers.

Affected Versions

3.0.0-3.0.6, 2.7.0-2.7.11, 2.6.0-2.6.14, 2.5.0-2.5.14 and older unsupported versions

Code Fix Example

Spring Boot API Security Remediation
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class App {
  public static void main(String[] args) { SpringApplication.run(App.class, args); }

  // path constants to avoid inline string literals in annotations
  private static final String VULN_PATH = "/vulnerable/process";
  private static final String FIX_PATH  = "/fixed/process";

  @RestController
  public static class VulnerableController {
    @GetMapping(VULN_PATH)
    public String vulnerableProcess(@RequestParam Map<String, String> params) {
      StringBuilder sb = new StringBuilder();
      // Vulnerable pattern: no bounding on CPU/memory work per request
      for (String v : params.values()) {
        sb.append(v);
        // simulate expensive work per parameter (unbounded in practice)
        for (int i = 0; i < 1000; i++) {
          sb.append(i);
        }
      }
      return "Processed " + params.size() + " params, chars=" + sb.length();
    }
  }

  @RestController
  public static class FixedController {
    private static final int MAX_PARAM_COUNT = 1000;
    private static final int MAX_TOTAL_PARAM_LENGTH = 10000; // total chars across all params

    @GetMapping(FIX_PATH)
    public String fixedProcess(@RequestParam Map<String, String> params) {
      if (params.size() > MAX_PARAM_COUNT) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
      }
      long totalLen = 0;
      for (String s : params.values()) totalLen += s.length();
      if (totalLen > MAX_TOTAL_PARAM_LENGTH) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
      }
      StringBuilder sb = new StringBuilder();
      for (String v : params.values()) sb.append(v);
      return "Processed " + params.size() + " params, chars=" + sb.length();
    }
  }

  @Component
  public static class LimitRequestSizeFilter implements Filter {
    private static final long MAX_REQUEST_SIZE = 2 * 1024 * 1024; // 2 MB

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      HttpServletRequest httpReq = (HttpServletRequest) request;
      long contentLength = httpReq.getContentLengthLong();
      if (contentLength > MAX_REQUEST_SIZE) {
        HttpServletResponse httpResp = (HttpServletResponse) response;
        httpResp.setStatus(HttpStatus.REQUEST_ENTITY_TOO_LARGE.value());
        return;
      }
      chain.doFilter(request, response);
    }
  }
}

CVE References

Choose which optional cookies to allow. You can change this any time.