Status: Patched
This vulnerability has been verified as resolved and deployed.
SCGI unbuffered mode sent truncated CONTENT_LENGTH causing backend desync
Summary
NGINX SCGI used buffered-prefix body length in unbuffered mode; fix now uses canonical content length inputs
In the SCGI module, ngx_http_scgi_create_request() historically derived CONTENT_LENGTH by summing the currently buffered request-body chain (r->upstream->request_bufs). This behavior was introduced to support chunked-body accounting, but it becomes inaccurate when scgi_request_buffering off is used and the body is still streaming.
With unbuffered request forwarding, only an early body prefix may be available when SCGI headers are serialized. The emitted SCGI netstring can therefore advertise a smaller CONTENT_LENGTH than the client-declared body size. Downstream SCGI backends that keep connections open and parse trailing bytes as another request can become desynchronized.
Upstream NGINX accepted this as a protocol-framing bug and explicitly classified it as a regular bugfix (not an NGINX security issue), noting SCGI is specified as one request per connection. The fix development is tracked in PR #1118 and implemented in commit fe2d109.
CVSS Score
Vulnerability Location
Sink-to-Source Analysis
When scgi_request_buffering off is configured and a non-chunked request body is passed, SCGI switches to no-buffering mode and may start upstream request creation before the full body is read.
Legacy logic computed content_length_n from only the currently buffered body chain, which can represent just a prefix in unbuffered mode.
That truncated number was serialized into SCGI CONTENT_LENGTH, creating framing mismatch versus the full HTTP request body.
Patch fe2d109 now prefers original Content-Length header value, then content_length_n from body filters, and finally 0, aligning SCGI framing with canonical length sources.
Impact Analysis
Critical Impact
In affected backend implementations this can cause backend request desynchronization and request-smuggling-like behavior. NGINX maintainers classified the issue as a regular bugfix rather than a security vulnerability in NGINX itself, because SCGI specifies one request per connection and does not define semantics for extra bytes.
Attack Surface
NGINX deployments using SCGI with scgi_request_buffering off and backends that keep connections alive while interpreting trailing bytes as additional SCGI requests.
Preconditions
Attacker can send a POST with explicit Content-Length and control body timing; backend behavior must be non-standard (or extension-like) by processing extra bytes as another request on the same connection.
Proof of Concept
Environment Setup
Requirements: Python 3, C toolchain, and OpenSSL/PCRE2 dev libraries.
Build NGINX from source:
Target Configuration
Create nginx.conf (self-contained repro config):
Create scgi_backend.py:
Exploit Delivery
Create poc_smuggle.py:
Run end-to-end:
Cleanup:
Outcome
The PoC demonstrates message-framing mismatch in unbuffered SCGI mode and backend desync risk when a backend accepts multiple parsed units on one connection.
Expected Response:
Backend logs show a first SCGI request with truncated CONTENT_LENGTH followed by attacker-controlled residual bytes interpreted as a second request in permissive backends:
