HTTP Request Smuggling Using Chunk Extensions (CVE-2025-55315)
A deep dive into the ASP.NET Core recent disclosure
Executive Summary
HTTP request smuggling remains one of the nastier protocol-level surprises: it happens when different components in the HTTP chain disagree about where one request ends and the next begins. A recent, high-visibility ASP.NET Core disclosure brought one particular flavor of this problem into the spotlight: attackers abusing chunk extensions in chunked transfer encoding to craft ambiguous request boundaries. The vulnerability was assigned a very high severity (CVSS 9.9) by Microsoft, their highest for ASP.NET Core to date.
This article explains what chunk extensions are, why they can be abused for smuggling, how the recent ASP.NET Core issue fits into the bigger picture, and what defenders, implementers, and F5 customers should consider: particularly regarding HTTP normalization, compliance settings, and protection coverage across F5 Advanced WAF, NGINX App Protect, and Distributed Cloud.
Background: What Are Chunk Extensions?
In HTTP/1.1, chunked transfer encoding (via Transfer-Encoding: chunked) allows the body of a message to be sent in a sequence of chunks, each preceded by its size in hex, terminated by a zero-length chunk. The specification also allows chunk extensions to be appended after the chunk length, e.g.:
Figure 1: chunk extensionsIn theory, chunk extensions were meant for metadata or transfer-layer options: for example, integrity checks or special directives.
But in practice, they’re almost never used by legitimate clients or servers: many HTTP libraries ignore or inconsistently handle them, and this inconsistency across intermediaries (proxies and servers) can serve as a source of request smuggling vulnerabilities.
But if a lot of servers and proxies ignore it, why would that even be an issue?
Let’s see.
Root Cause Analysis for CVE-2025-55315
The CVE description reads:
“Inconsistent interpretation of HTTP requests (‘HTTP request/response smuggling’) in ASP.NET Core allows an authorized attacker to bypass a security feature over a network.”
Examining the GitHub commit reveals a relatively straightforward fix. In essence, the patch adjusts the chunk-extension parser to correctly handle \r\n line endings and to throw an error if either \r or \n appears unpaired. Additionally, a new flag was introduced for backward compatibility.
As expected, the vulnerable logic resides in the ParseExtension function. The new InsecureChunkedParsing flag preserves legacy behavior - but it must be explicitly enabled, since that mode reflects the prior (and now considered insecure) implementation.
Figure 2: Chunk extensions parsingPreviously, the parser looked only for the carriage return (\r) character to determine the end of a line.
In the updated implementation, it now checks for either a line feed (\n) or a carriage
return (\r).
Next, we encounter the following condition:
Figure 3: Line termination checkThe syntax may look a bit dense, but the logic is straightforward.
In short, they retained the old insecure behavior when the InsecureChunkedParsing flag is enabled, which is checking the presence of \n only after encountering \r .
This is problematic because it allows injecting a single \r or \n inside the chunk extension.
In depth, the vulnerable condition, suffixSpan[1] == ByteLF, mirrors the old behavior - it verifies that the second character is \n. We reach this part only if we previously saw \r.
The new condition validates that the last two characters of the chunk extension are \r\n. Remember that in the new version, we reach this part when encountering either \r or \n.
The fixed condition ensures that if an attacker tries to inject a single \r or \n somewhere within the chunk extension, the check will fail - the condition will evaluate to false.
When that happens, and if the backward-compatibility flag is not enabled, the parser throws an exception: Bad chunk extension.
And what happened before the patch if the character following \r wasn’t \n?
Figure 4: \r without \n, the vulnerable codeThey simply continued parsing, making the following characters part of the chunk extension. That means that a chunk extension could include line terminator characters.
The attack affecting unpatched ASP.NET Core applications is HTTP request smuggling via chunk extensions, a technique explained clearly and in depth in this article, which we’ll briefly summarize in this post.
Request smuggling using chunk extensions variants
Before diving into the different chunk-extension smuggling variants, it’s worth recalling the classic Content-Length / Transfer-Encoding (CL.TE and TE.CL) request smuggling techniques. These rely on discrepancies between how proxies and back-end servers interpret message boundaries: one trusts the Content-Length, the other trusts Transfer-Encoding, allowing attackers to sneak an extra request inside a single HTTP message.
If you’re not familiar with CL.TE and TE.CL and other variants, this article gives an excellent overview of how these desync vulnerabilities work in practice.
- TERM.EXT (terminator - extension mismatch): The proxy treats a line terminator (usually \n) inside a chunk extension as the end of the chunk header, while the backend treats the same bytes as part of the extension.
- EXT.TERM (extension - terminator mismatch)
The proxy treats only \r\n sequence as the end of the chunk header, while the backend treats the line terminator character inside the chunk extension as the end of the chunk header. Figure 6: EXT.TERM example, from the referenced article
The ASP.NET Core issue
Previously, ASP.NET Core allowed lone \r or \n characters to appear within a chunk extension if the line ended with \r\n, placing it in the EXT category.
If a proxy ahead has TERM behavior (treating \n as line end), their parsing mismatch can enable request smuggling.
The figure shows an example malicious request that exploits this parsing mismatch.
Figure 7: EXT.TERM malicious request example, from the referenced articleThe proxy treats a lone \n as the end of the chunk extension. As a result, the bytes xx become the start of the body and 47 is interpreted as the size of the following chunk. If the proxy forwards the request unchanged (i.e., it does not strip the extension), those next chunks can effectively carry a second, smuggled request destined for an internal endpoint that the proxy would normally block.
When Kestrel (the ASP.NET Core backend) receives that same raw stream, it enforces a strict \r\n terminator for extensions. Because the backend searches specifically for the \r\n sequence, it parses the received stream differently - splitting the forwarded data into two requests (the extension content, 2;\nxx is treated as a chunk header + chunk body).
The end result: a GET /admin request can reach the backend, even though the proxy would have blocked such a request if it had been observed as a separate, external request.
F5 WAF Protections
NGINX App Protect and F5 Distributed Cloud
NGINX App Protect and F5 Distributed Cloud (XC) normalize incoming HTTP requests and do not support chunk extensions.
This means that any request arriving at NAP or XC with chunk extensions will have those extensions removed before being forwarded to the backend server.
As a result, both NAP and XC are inherently protected against this class of chunk-extension smuggling attacks by design.
To illustrate this, let’s revisit the example from the referenced article.
Figure 8: malicious request example, from the referenced articleNGINX, which treats a lone \n as a valid line terminator, falls under the TERM category. When this request is sent through NAP, it is parsed and normalized accordingly - effectively split into two separate requests:
Figure 9: The first requestFigure 10: The second requestWhat does this mean?
NAP does not forward the request the same as it arrived. It normalizes the message by stripping out any chunk extensions, replacing the Transfer-Encoding header with a Content-Length, and ensuring the body is parsed deterministically - leaving no room for ambiguity or smuggling.
If a proxy precedes NAP and interprets the traffic as a single request, NAP will safely split and sanitize it.
F5 Distributed Cloud (XC) doesn’t treat lone \n as line terminators and also discards chunk extensions entirely.
Advanced WAF
Advanced WAF does not support chunk extensions.
Requests containing a chunk header that is too long (more than 10 bytes) are treated as unparsable and trigger an HTTP compliance violation. To improve detection, we’ve released a new attack signature, “ASP.NET Core Request Smuggling - 200020232", which helps identify and block malicious attempts that rely on chunk extensions.
Conclusions
HTTP request smuggling via chunk extensions remains a very real threat, even in modern stacks. The disclosure of CVE-2025-55315 in the Kestrel web server underlines this: a seemingly small parsing difference (how \r, \n, and \r\n are treated in chunk extensions) can allow an attacker to conceal a second request within a legitimate one, enabling account takeover, code injection, SSRF, and many other severe attacks.
This case offers a great reminder: don’t assume that because “nobody uses chunk extensions” they cannot be weaponized.
And of course - use HTTP/2. Its binary framing model eliminates chunked encoding altogether, removing the ambiguity that makes these attacks possible in HTTP/1.1.