2018-12-19 10:49:39 +00:00
|
|
|
varnishtest "HTTP request tests: H2 to H1 (HTX and legacy mode)"
|
2018-12-13 18:35:29 +00:00
|
|
|
|
|
|
|
# Run it with HAPROXY_PROGRAM=$PWD/haproxy varnishtest -l -k -t 1 "$1"
|
|
|
|
|
|
|
|
feature ignore_unknown_macro
|
|
|
|
|
|
|
|
# synchronize requests between streams
|
|
|
|
barrier b1 cond 2 -cyclic
|
|
|
|
barrier b2 cond 2 -cyclic
|
|
|
|
barrier b3 cond 2 -cyclic
|
|
|
|
barrier b4 cond 2 -cyclic
|
BUG/MAJOR: http: reject any empty content-length header value
The content-length header parser has its dedicated function, in order
to take extreme care about invalid, unparsable, or conflicting values.
But there's a corner case in it, by which it stops comparing values
when reaching the end of the header. This has for a side effect that
an empty value or a value that ends with a comma does not deserve
further analysis, and it acts as if the header was absent.
While this is not necessarily a problem for the value ending with a
comma as it will be cause a header folding and will disappear, it is a
problem for the first isolated empty header because this one will not
be recontructed when next ones are seen, and will be passed as-is to the
backend server. A vulnerable HTTP/1 server hosted behind haproxy that
would just use this first value as "0" and ignore the valid one would
then not be protected by haproxy and could be attacked this way, taking
the payload for an extra request.
In field the risk depends on the server. Most commonly used servers
already have safe content-length parsers, but users relying on haproxy
to protect a known-vulnerable server might be at risk (and the risk of
a bug even in a reputable server should never be dismissed).
A configuration-based work-around consists in adding the following rule
in the frontend, to explicitly reject requests featuring an empty
content-length header that would have not be folded into an existing
one:
http-request deny if { hdr_len(content-length) 0 }
The real fix consists in adjusting the parser so that it always expects a
value at the beginning of the header or after a comma. It will now reject
requests and responses having empty values anywhere in the C-L header.
This needs to be backported to all supported versions. Note that the
modification was made to functions h1_parse_cont_len_header() and
http_parse_cont_len_header(). Prior to 2.8 the latter was in
h2_parse_cont_len_header(). One day the two should be refused but the
former is also used by Lua.
The HTTP messaging reg-tests were completed to test these cases.
Thanks to Ben Kallus of Dartmouth College and Narf Industries for
reporting this! (this is in GH #2237).
2023-08-09 06:32:48 +00:00
|
|
|
barrier b5 cond 2 -cyclic
|
|
|
|
barrier b6 cond 2 -cyclic
|
2018-12-13 18:35:29 +00:00
|
|
|
|
|
|
|
server s1 {
|
|
|
|
rxreq
|
|
|
|
txresp \
|
|
|
|
-status 200 \
|
|
|
|
-body "response 1"
|
|
|
|
|
|
|
|
barrier b2 sync
|
|
|
|
rxreq
|
|
|
|
txresp \
|
|
|
|
-status 200 \
|
|
|
|
-body "response 2"
|
|
|
|
|
|
|
|
barrier b3 sync
|
|
|
|
rxreq
|
|
|
|
txresp \
|
|
|
|
-status 200 \
|
|
|
|
-body "response 3"
|
|
|
|
|
|
|
|
barrier b4 sync
|
2019-01-24 10:49:37 +00:00
|
|
|
# the next request is never received
|
BUG/MAJOR: http: reject any empty content-length header value
The content-length header parser has its dedicated function, in order
to take extreme care about invalid, unparsable, or conflicting values.
But there's a corner case in it, by which it stops comparing values
when reaching the end of the header. This has for a side effect that
an empty value or a value that ends with a comma does not deserve
further analysis, and it acts as if the header was absent.
While this is not necessarily a problem for the value ending with a
comma as it will be cause a header folding and will disappear, it is a
problem for the first isolated empty header because this one will not
be recontructed when next ones are seen, and will be passed as-is to the
backend server. A vulnerable HTTP/1 server hosted behind haproxy that
would just use this first value as "0" and ignore the valid one would
then not be protected by haproxy and could be attacked this way, taking
the payload for an extra request.
In field the risk depends on the server. Most commonly used servers
already have safe content-length parsers, but users relying on haproxy
to protect a known-vulnerable server might be at risk (and the risk of
a bug even in a reputable server should never be dismissed).
A configuration-based work-around consists in adding the following rule
in the frontend, to explicitly reject requests featuring an empty
content-length header that would have not be folded into an existing
one:
http-request deny if { hdr_len(content-length) 0 }
The real fix consists in adjusting the parser so that it always expects a
value at the beginning of the header or after a comma. It will now reject
requests and responses having empty values anywhere in the C-L header.
This needs to be backported to all supported versions. Note that the
modification was made to functions h1_parse_cont_len_header() and
http_parse_cont_len_header(). Prior to 2.8 the latter was in
h2_parse_cont_len_header(). One day the two should be refused but the
former is also used by Lua.
The HTTP messaging reg-tests were completed to test these cases.
Thanks to Ben Kallus of Dartmouth College and Narf Industries for
reporting this! (this is in GH #2237).
2023-08-09 06:32:48 +00:00
|
|
|
|
|
|
|
barrier b5 sync
|
|
|
|
# the next request is never received
|
|
|
|
|
|
|
|
barrier b6 sync
|
|
|
|
# the next request is never received
|
2018-12-13 18:35:29 +00:00
|
|
|
} -repeat 2 -start
|
|
|
|
|
|
|
|
haproxy h1 -conf {
|
2021-05-09 12:41:41 +00:00
|
|
|
global
|
|
|
|
# WT: limit false-positives causing "HTTP header incomplete" due to
|
|
|
|
# idle server connections being randomly used and randomly expiring
|
|
|
|
# under us.
|
|
|
|
tune.idle-pool.shared off
|
|
|
|
|
2018-12-13 18:35:29 +00:00
|
|
|
defaults
|
|
|
|
#log stdout format raw daemon
|
|
|
|
mode http
|
2018-12-14 08:51:48 +00:00
|
|
|
option http-buffer-request
|
2021-11-18 16:46:22 +00:00
|
|
|
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
|
|
|
|
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
|
|
|
|
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
|
2018-12-13 18:35:29 +00:00
|
|
|
|
|
|
|
listen feh1
|
|
|
|
bind "fd@${feh1}"
|
|
|
|
bind "fd@${feh2}" proto h2
|
|
|
|
server s1 ${s1_addr}:${s1_port}
|
|
|
|
} -start
|
|
|
|
|
|
|
|
client c1h2 -connect ${h1_feh2_sock} {
|
|
|
|
txpri
|
|
|
|
stream 0 {
|
|
|
|
txsettings
|
|
|
|
rxsettings
|
|
|
|
txsettings -ack
|
|
|
|
rxsettings
|
|
|
|
expect settings.ack == true
|
|
|
|
} -run
|
|
|
|
|
|
|
|
# first request is valid
|
|
|
|
stream 1 {
|
|
|
|
txreq \
|
|
|
|
-req "GET" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test1.html"
|
|
|
|
rxhdrs
|
|
|
|
expect resp.status == 200
|
|
|
|
rxdata -all
|
|
|
|
expect resp.body == "response 1"
|
|
|
|
} -run
|
|
|
|
|
|
|
|
# second request is valid and advertises C-L:0
|
|
|
|
stream 3 {
|
|
|
|
barrier b2 sync
|
|
|
|
txreq \
|
|
|
|
-req "GET" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test2.html" \
|
|
|
|
-hdr "content-length" "0"
|
|
|
|
rxhdrs
|
|
|
|
expect resp.status == 200
|
|
|
|
rxdata -all
|
|
|
|
expect resp.body == "response 2"
|
|
|
|
} -run
|
|
|
|
|
|
|
|
# third request sends a body with a GET
|
|
|
|
stream 5 {
|
|
|
|
barrier b3 sync
|
|
|
|
txreq \
|
|
|
|
-req "GET" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test3.html" \
|
|
|
|
-nostrend \
|
|
|
|
-body "this must be delivered, like it or not"
|
|
|
|
rxwinup
|
|
|
|
rxhdrs
|
|
|
|
expect resp.status == 200
|
|
|
|
rxdata -all
|
|
|
|
expect resp.body == "response 3"
|
|
|
|
} -run
|
|
|
|
|
2019-01-24 10:49:37 +00:00
|
|
|
# fourth request is valid and advertises C-L:2, and close, and is
|
|
|
|
# followed by a string "this is not sent\r\n\r\n" which causes a
|
|
|
|
# stream error of type PROTOCOL_ERROR.
|
2018-12-13 18:35:29 +00:00
|
|
|
stream 7 {
|
|
|
|
barrier b4 sync
|
|
|
|
txreq \
|
|
|
|
-req "GET" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test4.html" \
|
2019-01-24 10:49:37 +00:00
|
|
|
-hdr "content-length" "2" \
|
2018-12-13 18:35:29 +00:00
|
|
|
-nostrend
|
|
|
|
txdata -data "this is sent and ignored"
|
2019-01-24 10:49:37 +00:00
|
|
|
rxrst
|
2018-12-13 18:35:29 +00:00
|
|
|
} -run
|
BUG/MAJOR: http: reject any empty content-length header value
The content-length header parser has its dedicated function, in order
to take extreme care about invalid, unparsable, or conflicting values.
But there's a corner case in it, by which it stops comparing values
when reaching the end of the header. This has for a side effect that
an empty value or a value that ends with a comma does not deserve
further analysis, and it acts as if the header was absent.
While this is not necessarily a problem for the value ending with a
comma as it will be cause a header folding and will disappear, it is a
problem for the first isolated empty header because this one will not
be recontructed when next ones are seen, and will be passed as-is to the
backend server. A vulnerable HTTP/1 server hosted behind haproxy that
would just use this first value as "0" and ignore the valid one would
then not be protected by haproxy and could be attacked this way, taking
the payload for an extra request.
In field the risk depends on the server. Most commonly used servers
already have safe content-length parsers, but users relying on haproxy
to protect a known-vulnerable server might be at risk (and the risk of
a bug even in a reputable server should never be dismissed).
A configuration-based work-around consists in adding the following rule
in the frontend, to explicitly reject requests featuring an empty
content-length header that would have not be folded into an existing
one:
http-request deny if { hdr_len(content-length) 0 }
The real fix consists in adjusting the parser so that it always expects a
value at the beginning of the header or after a comma. It will now reject
requests and responses having empty values anywhere in the C-L header.
This needs to be backported to all supported versions. Note that the
modification was made to functions h1_parse_cont_len_header() and
http_parse_cont_len_header(). Prior to 2.8 the latter was in
h2_parse_cont_len_header(). One day the two should be refused but the
former is also used by Lua.
The HTTP messaging reg-tests were completed to test these cases.
Thanks to Ben Kallus of Dartmouth College and Narf Industries for
reporting this! (this is in GH #2237).
2023-08-09 06:32:48 +00:00
|
|
|
|
|
|
|
# fifth request is invalid and advertises an invalid C-L ending with an
|
|
|
|
# empty value, which results in a stream error.
|
|
|
|
stream 9 {
|
|
|
|
barrier b5 sync
|
|
|
|
txreq \
|
|
|
|
-req "GET" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test5.html" \
|
|
|
|
-hdr "content-length" "0," \
|
|
|
|
-nostrend
|
|
|
|
rxrst
|
|
|
|
} -run
|
|
|
|
|
|
|
|
# sixth request is invalid and advertises an empty C-L, which results
|
|
|
|
# in a stream error.
|
|
|
|
stream 11 {
|
|
|
|
barrier b6 sync
|
|
|
|
txreq \
|
|
|
|
-req "GET" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test6.html" \
|
|
|
|
-hdr "content-length" "" \
|
|
|
|
-nostrend
|
|
|
|
rxrst
|
|
|
|
} -run
|
2018-12-13 18:35:29 +00:00
|
|
|
} -run
|
|
|
|
|
|
|
|
# HEAD requests : don't work well yet
|
|
|
|
#client c2h2 -connect ${h1_feh2_sock} {
|
|
|
|
# txpri
|
|
|
|
# stream 0 {
|
|
|
|
# txsettings
|
|
|
|
# rxsettings
|
|
|
|
# txsettings -ack
|
|
|
|
# rxsettings
|
|
|
|
# expect settings.ack == true
|
|
|
|
# } -run
|
|
|
|
#
|
|
|
|
# # first request is valid
|
|
|
|
# stream 1 {
|
|
|
|
# txreq \
|
|
|
|
# -req "HEAD" \
|
|
|
|
# -scheme "https" \
|
|
|
|
# -url "/test11.html"
|
|
|
|
# rxhdrs
|
|
|
|
# expect resp.status == 200
|
|
|
|
# rxdata -all
|
|
|
|
# expect resp.bodylen == 0
|
|
|
|
# } -run
|
|
|
|
#
|
|
|
|
# # second request is valid and advertises C-L:0
|
|
|
|
# stream 3 {
|
|
|
|
# barrier b2 sync
|
|
|
|
# txreq \
|
|
|
|
# -req "HEAD" \
|
|
|
|
# -scheme "https" \
|
|
|
|
# -url "/test12.html" \
|
|
|
|
# -hdr "content-length" "0"
|
|
|
|
# rxhdrs
|
|
|
|
# expect resp.status == 200
|
|
|
|
# rxdata -all
|
|
|
|
# expect resp.bodylen == 0
|
|
|
|
# } -run
|
|
|
|
#
|
|
|
|
# # third request sends a body with a GET
|
|
|
|
# stream 5 {
|
|
|
|
# barrier b3 sync
|
|
|
|
# txreq \
|
|
|
|
# -req "HEAD" \
|
|
|
|
# -scheme "https" \
|
|
|
|
# -url "/test13.html" \
|
|
|
|
# -nostrend \
|
|
|
|
# -body "this must be delivered, like it or not"
|
|
|
|
# rxwinup
|
|
|
|
# rxhdrs
|
|
|
|
# expect resp.status == 200
|
|
|
|
# rxdata -all
|
|
|
|
# expect resp.bodylen == 0
|
|
|
|
# } -run
|
|
|
|
#
|
|
|
|
# # fourth request is valid and advertises C-L:0, and close, and is
|
|
|
|
# # followed by a string "this is not sent\r\n\r\n" which must be
|
|
|
|
# # dropped.
|
|
|
|
# stream 7 {
|
|
|
|
# barrier b4 sync
|
|
|
|
# txreq \
|
|
|
|
# -req "HEAD" \
|
|
|
|
# -scheme "https" \
|
|
|
|
# -url "/test14.html" \
|
|
|
|
# -hdr "content-length" "0" \
|
|
|
|
# -nostrend
|
|
|
|
# txdata -data "this is sent and ignored"
|
|
|
|
# rxwinup
|
|
|
|
# rxhdrs
|
|
|
|
# expect resp.status == 200
|
|
|
|
# rxdata -all
|
|
|
|
# expect resp.bodylen == 0
|
|
|
|
# } -run
|
|
|
|
#} -run
|
|
|
|
|
|
|
|
# POST requests
|
|
|
|
client c3h2 -connect ${h1_feh2_sock} {
|
|
|
|
txpri
|
|
|
|
stream 0 {
|
|
|
|
txsettings
|
|
|
|
rxsettings
|
|
|
|
txsettings -ack
|
|
|
|
rxsettings
|
|
|
|
expect settings.ack == true
|
|
|
|
} -run
|
|
|
|
|
|
|
|
# first request is valid
|
|
|
|
stream 1 {
|
|
|
|
txreq \
|
|
|
|
-req "POST" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test21.html"
|
|
|
|
rxhdrs
|
|
|
|
expect resp.status == 200
|
|
|
|
rxdata -all
|
|
|
|
expect resp.body == "response 1"
|
|
|
|
} -run
|
|
|
|
|
|
|
|
# second request is valid and advertises C-L:0
|
|
|
|
stream 3 {
|
|
|
|
barrier b2 sync
|
|
|
|
txreq \
|
|
|
|
-req "POST" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test22.html" \
|
|
|
|
-hdr "content-length" "0"
|
|
|
|
rxhdrs
|
|
|
|
expect resp.status == 200
|
|
|
|
rxdata -all
|
|
|
|
expect resp.body == "response 2"
|
|
|
|
} -run
|
|
|
|
|
|
|
|
# third request sends a body with a GET
|
|
|
|
stream 5 {
|
|
|
|
barrier b3 sync
|
|
|
|
txreq \
|
|
|
|
-req "POST" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test23.html" \
|
|
|
|
-nostrend \
|
|
|
|
-body "this must be delivered, like it or not"
|
|
|
|
rxwinup
|
|
|
|
rxhdrs
|
|
|
|
expect resp.status == 200
|
|
|
|
rxdata -all
|
|
|
|
expect resp.body == "response 3"
|
|
|
|
} -run
|
|
|
|
|
2019-01-24 10:49:37 +00:00
|
|
|
# fourth request is valid and advertises C-L:2, and close, and is
|
|
|
|
# followed by a string "this is not sent\r\n\r\n" which results
|
|
|
|
# in a stream error.
|
2018-12-13 18:35:29 +00:00
|
|
|
stream 7 {
|
|
|
|
barrier b4 sync
|
|
|
|
txreq \
|
|
|
|
-req "POST" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test24.html" \
|
2019-01-24 10:49:37 +00:00
|
|
|
-hdr "content-length" "2" \
|
2018-12-13 18:35:29 +00:00
|
|
|
-nostrend
|
|
|
|
txdata -data "this is sent and ignored"
|
2019-01-24 10:49:37 +00:00
|
|
|
rxrst
|
2018-12-13 18:35:29 +00:00
|
|
|
} -run
|
BUG/MAJOR: http: reject any empty content-length header value
The content-length header parser has its dedicated function, in order
to take extreme care about invalid, unparsable, or conflicting values.
But there's a corner case in it, by which it stops comparing values
when reaching the end of the header. This has for a side effect that
an empty value or a value that ends with a comma does not deserve
further analysis, and it acts as if the header was absent.
While this is not necessarily a problem for the value ending with a
comma as it will be cause a header folding and will disappear, it is a
problem for the first isolated empty header because this one will not
be recontructed when next ones are seen, and will be passed as-is to the
backend server. A vulnerable HTTP/1 server hosted behind haproxy that
would just use this first value as "0" and ignore the valid one would
then not be protected by haproxy and could be attacked this way, taking
the payload for an extra request.
In field the risk depends on the server. Most commonly used servers
already have safe content-length parsers, but users relying on haproxy
to protect a known-vulnerable server might be at risk (and the risk of
a bug even in a reputable server should never be dismissed).
A configuration-based work-around consists in adding the following rule
in the frontend, to explicitly reject requests featuring an empty
content-length header that would have not be folded into an existing
one:
http-request deny if { hdr_len(content-length) 0 }
The real fix consists in adjusting the parser so that it always expects a
value at the beginning of the header or after a comma. It will now reject
requests and responses having empty values anywhere in the C-L header.
This needs to be backported to all supported versions. Note that the
modification was made to functions h1_parse_cont_len_header() and
http_parse_cont_len_header(). Prior to 2.8 the latter was in
h2_parse_cont_len_header(). One day the two should be refused but the
former is also used by Lua.
The HTTP messaging reg-tests were completed to test these cases.
Thanks to Ben Kallus of Dartmouth College and Narf Industries for
reporting this! (this is in GH #2237).
2023-08-09 06:32:48 +00:00
|
|
|
|
|
|
|
# fifth request is invalid and advertises invalid C-L ending with an
|
|
|
|
# empty value, which results in a stream error.
|
|
|
|
stream 9 {
|
|
|
|
barrier b5 sync
|
|
|
|
txreq \
|
|
|
|
-req "POST" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test25.html" \
|
|
|
|
-hdr "content-length" "0," \
|
|
|
|
-nostrend
|
|
|
|
rxrst
|
|
|
|
} -run
|
|
|
|
|
|
|
|
# sixth request is invalid and advertises an empty C-L, which results
|
|
|
|
# in a stream error.
|
|
|
|
stream 11 {
|
|
|
|
barrier b6 sync
|
|
|
|
txreq \
|
|
|
|
-req "POST" \
|
|
|
|
-scheme "https" \
|
|
|
|
-url "/test26.html" \
|
|
|
|
-hdr "content-length" "" \
|
|
|
|
-nostrend
|
|
|
|
rxrst
|
|
|
|
} -run
|
2018-12-13 18:35:29 +00:00
|
|
|
} -run
|