CVE-2026-23941 Overview
CVE-2026-23941 is an HTTP Request Smuggling vulnerability affecting the Erlang OTP inets httpd module. The vulnerability stems from the server's failure to reject or normalize duplicate Content-Length headers in HTTP requests. When processing requests with multiple Content-Length headers, the Erlang httpd server uses the earliest value for body parsing, while common reverse proxies such as nginx, Apache httpd, and Envoy honor the last Content-Length value. This inconsistency violates RFC 9112 Section 6.3 and enables front-end/back-end desynchronization attacks.
The vulnerability is specifically associated with the file lib/inets/src/http_server/httpd_request.erl and the httpd_request:parse_headers/7 routine, where header validation fails to detect and reject duplicate Content-Length headers.
Critical Impact
Attackers can exploit this desynchronization to inject malicious requests into the HTTP pipeline, potentially bypassing security controls, hijacking user sessions, poisoning web caches, or accessing unauthorized data through request smuggling attacks.
Affected Products
- Erlang OTP versions 17.0 through 28.4.1 (prior to patch)
- Erlang OTP versions 27.x (prior to 27.3.4.9)
- Erlang OTP versions 26.x (prior to 26.2.5.18)
- Erlang inets module versions 5.10 through 9.6.1 (prior to patch)
- Erlang inets module versions 9.3.x (prior to 9.3.2.3)
- Erlang inets module versions 9.1.x (prior to 9.1.0.5)
Discovery Timeline
- 2026-03-13 - CVE-2026-23941 published to NVD
- 2026-03-16 - Last updated in NVD database
Technical Details for CVE-2026-23941
Vulnerability Analysis
HTTP Request Smuggling vulnerabilities arise when front-end and back-end servers interpret HTTP request boundaries differently. In this case, the Erlang OTP httpd server processes the first Content-Length header encountered in a request, while most reverse proxies and load balancers follow the convention of using the last Content-Length header value. This discrepancy allows an attacker to craft a request that appears as one complete request to the front-end proxy but is interpreted as two separate requests by the back-end Erlang server.
The attack exploits this parsing inconsistency by sending a request with two different Content-Length values. The front-end proxy reads the body according to the last header value, while the back-end server reads according to the first. Any remaining bytes after the back-end finishes parsing are left in the connection buffer and prepended to the next request, effectively allowing the attacker to inject arbitrary request content.
Root Cause
The root cause lies in the httpd_request:parse_headers/7 function within lib/inets/src/http_server/httpd_request.erl. The original implementation of the check_header function did not validate whether a Content-Length header had already been received. This allowed requests containing duplicate Content-Length headers to be processed without rejection, contrary to RFC 9112 Section 6.3 which mandates that such requests must be rejected as invalid.
The fix modifies the check_header function to accept an additional Headers parameter, enabling it to check for pre-existing Content-Length headers and return an appropriate error when duplicates are detected.
Attack Vector
The attack requires network access to a system where Erlang OTP httpd is deployed behind a reverse proxy. An attacker crafts an HTTP request containing duplicate Content-Length headers with different values. Due to the parsing discrepancy, the attacker can inject a malicious request that gets queued for the next legitimate user connection, enabling session hijacking, cache poisoning, or bypassing authentication controls.
// Security patch in lib/inets/src/http_server/httpd_request.erl
// Source: https://github.com/erlang/otp/commit/a4b46336fd25aa100ac602eb9a627aaead7eda18
Headers),
{ok, list_to_tuple(lists:reverse([Body, {http_request:headers(FinalHeaders, #http_request_h{}), FinalHeaders} | Result]))};
NewHeader ->
- case check_header(NewHeader, Options) of
+ case check_header(NewHeader, Headers, Options) of
ok ->
FinalHeaders = lists:filtermap(fun(H) ->
httpd_custom:customize_headers(Customize, request_header, H)
The patch adds the Headers parameter to check_header, allowing the function to detect duplicate Content-Length headers and properly reject malformed requests.
// Security patch in lib/inets/src/http_server/httpd_request_handler.erl
// Source: https://github.com/erlang/otp/commit/a4b46336fd25aa100ac602eb9a627aaead7eda18
httpd_response:send_status(NewModData, ErrCode, ErrStr, {max_size, MaxSize}),
{stop, normal, State#state{response_sent = true,
mod = NewModData}};
-
- {error, {version_error, ErrCode, ErrStr}, Version} ->
+ {error, {version_error, ErrCode, ErrStr}, Version} ->
NewModData = ModData#mod{http_version = Version},
httpd_response:send_status(NewModData, ErrCode, ErrStr),
{stop, normal, State#state{response_sent = true,
- mod = NewModData}};
+ mod = NewModData}};
+ {error, {bad_request, ErrCode, ErrStr}, Version} ->
+ NewModData = ModData#mod{http_version = Version},
+ httpd_response:send_status(NewModData, ErrCode, ErrStr),
+ {stop, normal, State#state{response_sent = true,
+ mod = NewModData}};
{http_chunk = Module, Function, Args} when ChunkState =/= undefined ->
NewState = handle_chunk(Module, Function, Args, State),
This change adds proper error handling for bad_request errors, ensuring that requests with duplicate Content-Length headers receive appropriate HTTP 400 Bad Request responses.
Detection Methods for CVE-2026-23941
Indicators of Compromise
- HTTP requests containing multiple Content-Length headers arriving at backend servers
- Unusual HTTP 400 responses following the patch deployment indicating blocked smuggling attempts
- Desynchronized request/response patterns in application logs where responses don't match expected requests
- Unexpected requests appearing in server logs that were not initiated by legitimate clients
Detection Strategies
- Implement deep packet inspection at the network perimeter to identify HTTP requests containing duplicate Content-Length headers
- Deploy web application firewalls (WAF) configured to reject requests with multiple Content-Length headers before they reach backend servers
- Monitor for anomalous request patterns where the timing or sequence of requests suggests pipeline manipulation
- Audit proxy and load balancer logs for requests that may indicate smuggling attempts
Monitoring Recommendations
- Enable verbose HTTP request logging on Erlang httpd servers to capture all incoming header information
- Configure alerts for requests containing duplicate HTTP headers, particularly Content-Length and Transfer-Encoding
- Implement correlation analysis between front-end proxy logs and back-end server logs to identify request boundary discrepancies
- Monitor for sudden increases in HTTP 400 Bad Request responses which may indicate ongoing exploitation attempts
How to Mitigate CVE-2026-23941
Immediate Actions Required
- Upgrade Erlang OTP to version 28.4.1, 27.3.4.9, or 26.2.5.18 or later depending on your major version branch
- Upgrade the inets module to version 9.6.1, 9.3.2.3, or 9.1.0.5 or later depending on your version branch
- Configure front-end proxies to normalize or reject requests with duplicate Content-Length headers as a defense-in-depth measure
- Review application logs for signs of past exploitation attempts
Patch Information
Erlang has released security patches addressing this vulnerability across multiple supported branches. The fixes are available in the following commits:
For complete details, refer to the GitHub Security Advisory and Erlang Version Documentation.
Workarounds
- Configure reverse proxies (nginx, Apache httpd, Envoy) to reject requests containing multiple Content-Length headers before forwarding to backend servers
- Implement a WAF rule to block requests with duplicate Content-Length or conflicting Content-Length and Transfer-Encoding headers
- If using nginx, add configuration to normalize request headers and reject malformed requests at the proxy layer
- Consider using HTTP/2 end-to-end where possible, as HTTP/2's binary framing protocol is not susceptible to this class of attack
# nginx configuration example to reject duplicate Content-Length headers
# Add to server or location block
# Reject requests with multiple Content-Length headers
if ($http_content_length ~* ",") {
return 400;
}
# Alternative: Use proxy_set_header to normalize headers
proxy_set_header Content-Length $content_length;
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.


