Skip to content

Content-Length and Transfer-Encoding conflicts #43523

@Tratcher

Description

@Tratcher

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2

A sender MUST NOT send a Content-Length header field in any message
that contains a Transfer-Encoding header field.

https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3

If a message is received with both a Transfer-Encoding and a
Content-Length header field, the Transfer-Encoding overrides the
Content-Length. Such a message might indicate an attempt to
perform request smuggling (Section 9.5) or response splitting
(Section 9.4) and ought to be handled as an error. A sender MUST
remove the received Content-Length field prior to forwarding such
a message downstream.

Kestrel follows this today by honoring the Transfer-Encoding: chunked and ignoring the Content-Length header. However, the Content-Length header is still present on the request and apps may still be consuming it.

if (headers.HasTransferEncoding)
{
var transferEncoding = headers.HeaderTransferEncoding;
var transferCoding = HttpHeaders.GetFinalTransferCoding(transferEncoding);
// https://tools.ietf.org/html/rfc7230#section-3.3.3
// If a Transfer-Encoding header field
// is present in a request and the chunked transfer coding is not
// the final encoding, the message body length cannot be determined
// reliably; the server MUST respond with the 400 (Bad Request)
// status code and then close the connection.
if (transferCoding != TransferCoding.Chunked)
{
KestrelBadHttpRequestException.Throw(RequestRejectionReason.FinalTransferCodingNotChunked, transferEncoding);
}
// TODO may push more into the wrapper rather than just calling into the message body
// NBD for now.
return new Http1ChunkedEncodingMessageBody(context, keepAlive);
}
if (headers.ContentLength.HasValue)
{
var contentLength = headers.ContentLength.Value;
if (contentLength == 0)
{
return keepAlive ? MessageBody.ZeroContentLengthKeepAlive : MessageBody.ZeroContentLengthClose;
}
return new Http1ContentLengthMessageBody(context, contentLength, keepAlive);
}

E.g. YARP has to check for this condition too to avoid a smuggling attempt when proxying.
https://github.com/microsoft/reverse-proxy/blob/06f28a36140543512a3fc3e1e9423094c43296ec/src/ReverseProxy/Forwarder/HttpTransformer.cs#L80-L92

App developers are not usually aware of spec nuances like this and write logic that makes decisions based on the Content-Length header when present. This can lead to application level functional and security issues like over/under buffering.

Expected Behavior

Proposal: Kestrel should implement the following spec guideline "A sender MUST remove the received Content-Length field prior to forwarding such a message downstream.", treating the application as the downstream entity. If there is a Transfer-Encoding header then kestrel should remove any Content-Length header to avoid app level confusion.

Since Kestrel avoids modifying the request in most cases, giving the app as much of the original data as possible, we want to preserve the Content-Length header value by moving it to x-Content-Length. This should avoid people consuming the unvalidated value by accident, but it's still available for compat reasons.

YARP could remove its mitigation after this change.

Steps To Reproduce

POST / HTTP/1.1
Content-Length: 500000
Transfer-Encoding: chunked

5
Hello
0

Exceptions (if any)

No response

.NET Version

No response

Anything else?

Recommended for .NET 8.

Metadata

Metadata

Labels

area-networkingIncludes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractionshelp wantedUp for grabs. We would accept a PR to help resolve this issue

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions