Skip to content
GitHub Repository Forum RSS-Newsfeed

Post Mortem: HTTP Request Smuggling Vulnerability

Julien Portalier

On 12 April 2026, we received a vulnerability report regarding an HTTP request smuggling vulnerability in HTTP::Server.

The issue was caused by the HTTP request parser prioritizing the Content-Length header over the Transfer-Encoding header, which can lead to desynchronization when a proxy that prioritizes the Transfer-Encoding header sits in front of HTTP::Server for example.

The vulnerability was patched in Crystal 1.20.0 and Crystal 1.19.2, following the mitigation from RFC 9112, Section 6.1 to always reject requests with both headers and to prioritize Transfer-Encoding then close the connection.

  • 2026-04-12: Gabriel Rodrigues reported the vulnerability to security@manas.tech

  • 2026-04-13: The report was shared with the Crystal Core Team and acknowledged to the reporter. The risk was assessed as low by team.

  • 2026-04-15: The patch of choice (rejecting requests with both headers) was confirmed as the mitigation. The reporter was informed about the assessment and mitigation strategy.

  • 2026-04-16: The patch was merged and a security advisory was published. Crystal 1.20.0 was released with the fix.

  • 2026-04-27: Crystal 1.19.2 was released with the backported patch.

The issue resided in the HTTP Request parser, specifically in the parse_headers_and_body method. The method used elsif to connect the checks for Content-Length and Transfer-Encoding, ensuring only one branch would execute. This meant that if Content-Length was present, the Transfer-Encoding branch would never be evaluated, regardless of its value.

elsif content_length = content_length(headers)
  body = FixedLengthContent.new(io, content_length)
elsif headers["Transfer-Encoding"]? == "chunked"
  body = ChunkedContent.new(io)

This behavior wasn’t compliant with the mitigation from RFC 9112, Section 6.1:

A server MAY reject a request that contains both Content-Length and Transfer-Encoding or process such a request in accordance with the Transfer-Encoding alone. Regardless, the server MUST close the connection after responding to such a request to avoid the potential attacks.

A server or client that receives an HTTP/1.0 message containing a Transfer-Encoding header field MUST treat the message as if the framing is faulty, even if a Content-Length is present, and close the connection after processing the message.

An attacker could craft a request like the following (no proxy required for demonstration):

POST / HTTP/1.1
Host: example.com
Content-Length: 4
Transfer-Encoding: chunked

43
GET /admin HTTP/1.1
Host: example.com

0

This request is ambiguous: Content-Length declares a 4 bytes body (43\r\n) while Transfer-Encoding would start reading. Since HTTP::Server favors Content-Length it would execute the POST / request then continue with the smuggled GET /admin request.

The vulnerability allowed attackers to inject arbitrary HTTP requests into the connection between a reverse proxy and a Crystal HTTP::Server. This could bypass authentication, rate limiting, or access control enforced at the proxy layer for example. However, exploitation required a vulnerable or non-compliant proxy, limiting the practical impact.

  • Can it be directly exploited? No.
  • Can it be exploited through a third party? Yes: vulnerable proxy or load balancer.
  • Are there possible exploits for the software flaw? Yes: CL.TE for example.
  • Are there known exploits for HTTP::Server + third party? No.
  • Are there known threats? No.

Risk: low.

The vulnerability stemmed from non-compliance with RFC 9112, which explicitly states that a server should either reject requests with both headers or prioritize Transfer-Encoding to mitigate request smuggling.

HTTP::Server thus now rejects requests with both Content-Length and Transfer-Encoding and the HTTP parser now prioritizes the Transfer-Encoding header before considering Content-Length, aligning with the RFC 9112.

This fix was included in Crystal 1.20.0 and 1.19.2.

  1. RFC Compliance Matters: Adhering to RFCs is critical for security and interoperability. Non-compliance can introduce vulnerabilities, even if they seem theoretical.

  2. Vulnerability in Chains: A vulnerable server may not be at risk, but a bug and a vulnerability in different software can lead to an exploitable attack.

  3. Training: We treated this as a serious issue, even if the practical impact was low, if only to prepare for more severe vulnerabilities in the future.

Thank you Gabriel Rodrigues for reporting this vulnerability!