Skip to content

Commit

Permalink
Reject HTTP/1 requests with both content-length and transfer-encoding
Browse files Browse the repository at this point in the history
The previous behavior was to accept them and drop the
content-length header as per the RFC recommendation.
But since this behavior is not normal it is safer to
just reject such requests than risk security issues.
  • Loading branch information
essen committed Jan 5, 2024
1 parent 5b2f600 commit 6ef79ae
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 15 deletions.
23 changes: 13 additions & 10 deletions src/cowboy_http.erl
Original file line number Diff line number Diff line change
Expand Up @@ -765,39 +765,42 @@ default_port(_) -> 80.
request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock, cert=Cert,
proxy_header=ProxyHeader, in_streamid=StreamID, in_state=
PS=#ps_header{method=Method, path=Path, qs=Qs, version=Version}},
Headers0, Host, Port) ->
Headers, Host, Port) ->
Scheme = case Transport:secure() of
true -> <<"https">>;
false -> <<"http">>
end,
{Headers, HasBody, BodyLength, TDecodeFun, TDecodeState} = case Headers0 of
{HasBody, BodyLength, TDecodeFun, TDecodeState} = case Headers of
#{<<"transfer-encoding">> := _, <<"content-length">> := _} ->
error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers}},
{stream_error, protocol_error,
'The request had both transfer-encoding and content-length headers. (RFC7230 3.3.3)'});
#{<<"transfer-encoding">> := TransferEncoding0} ->
try cow_http_hd:parse_transfer_encoding(TransferEncoding0) of
[<<"chunked">>] ->
{maps:remove(<<"content-length">>, Headers0),
true, undefined, fun cow_http_te:stream_chunked/2, {0, 0}};
{true, undefined, fun cow_http_te:stream_chunked/2, {0, 0}};
_ ->
error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers0}},
error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers}},
{stream_error, protocol_error,
'Cowboy only supports transfer-encoding: chunked. (RFC7230 3.3.1)'})
catch _:_ ->
error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers0}},
error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers}},
{stream_error, protocol_error,
'The transfer-encoding header is invalid. (RFC7230 3.3.1)'})
end;
#{<<"content-length">> := <<"0">>} ->
{Headers0, false, 0, undefined, undefined};
{false, 0, undefined, undefined};
#{<<"content-length">> := BinLength} ->
Length = try
cow_http_hd:parse_content_length(BinLength)
catch _:_ ->
error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers0}},
error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers}},
{stream_error, protocol_error,
'The content-length header is invalid. (RFC7230 3.3.2)'})
end,
{Headers0, true, Length, fun cow_http_te:stream_identity/2, {0, Length}};
{true, Length, fun cow_http_te:stream_identity/2, {0, Length}};
_ ->
{Headers0, false, 0, undefined, undefined}
{false, 0, undefined, undefined}
end,
Req0 = #{
ref => Ref,
Expand Down
11 changes: 6 additions & 5 deletions test/rfc7230_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1144,18 +1144,19 @@ reject_invalid_content_length(Config) ->
%with a message body too large must be rejected with a 413 status
%code and the closing of the connection. (RFC7230 3.3.2)

ignore_content_length_when_transfer_encoding(Config) ->
reject_when_both_content_length_and_transfer_encoding(Config) ->
doc("When a message includes both transfer-encoding and content-length "
"headers, the content-length header must be removed before processing "
"the request. (RFC7230 3.3.3)"),
#{code := 200, body := <<"Hello world!">>} = do_raw(Config, [
"headers, the message may be an attempt at request smuggling. It "
"must be rejected with a 400 status code and the closing of the "
"connection. (RFC7230 3.3.3)"),
#{code := 400, client := Client} = do_raw(Config, [
"POST /echo/read_body HTTP/1.1\r\n"
"Host: localhost\r\n"
"Transfer-encoding: chunked\r\n"
"Content-length: 12\r\n"
"\r\n"
"6\r\nHello \r\n5\r\nworld\r\n1\r\n!\r\n0\r\n\r\n"]),
ok.
{error, closed} = raw_recv(Client, 0, 1000).

%socket_error_while_reading_body(Config) ->
%If a socket error occurs while reading the body the server
Expand Down

0 comments on commit 6ef79ae

Please sign in to comment.