parent
c7329cd393
commit
31fa894416
@ -0,0 +1,52 @@
|
||||
From 2c681c6f30fb90adab4701e287ff7a7db669b2e7 Mon Sep 17 00:00:00 2001
|
||||
From: Christopher Faulet <cfaulet@haproxy.com>
|
||||
Date: Thu, 22 Dec 2022 09:47:01 +0100
|
||||
Subject: [PATCH] BUG/MEDIUM: mux-h2: Refuse interim responses with end-stream
|
||||
flag set
|
||||
|
||||
As state in RFC9113#8.1, HEADERS frame with the ES flag set that carries an
|
||||
informational status code is malformed. However, there is no test on this
|
||||
condition.
|
||||
|
||||
On 2.4 and higher, it is hard to predict consequences of this bug because
|
||||
end of the message is only reported with a flag. But on 2.2 and lower, it
|
||||
leads to a crash because there is an unexpected extra EOM block at the end
|
||||
of an interim response.
|
||||
|
||||
Now, when a ES flag is detected on a HEADERS frame for an interim message, a
|
||||
stream error is sent (RST_STREAM/PROTOCOL_ERROR).
|
||||
|
||||
This patch should solve the issue #1972. It should be backported as far as
|
||||
2.0.
|
||||
|
||||
(cherry picked from commit 827a6299e6995c5c3ba620d8b7cbacdaef67f2c4)
|
||||
Signed-off-by: Willy Tarreau <w@1wt.eu>
|
||||
(cherry picked from commit ebfae006c6b5de1d1fe0cdd51847ec1e39d5cf59)
|
||||
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
|
||||
(cherry picked from commit 84f5cba24f59b1c8339bb38323fcb01f434ba8e5)
|
||||
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
|
||||
(cherry picked from commit f5748a98c34bc889cae9386ca4f7073ab3f4c6b1)
|
||||
Signed-off-by: Christopher Faulet <cfaulet@haproxy.com>
|
||||
---
|
||||
src/mux_h2.c | 5 +++++
|
||||
1 file changed, 5 insertions(+)
|
||||
|
||||
diff --git a/src/mux_h2.c b/src/mux_h2.c
|
||||
index 7d23e5abd..7dbbfefec 100644
|
||||
--- a/src/mux_h2.c
|
||||
+++ b/src/mux_h2.c
|
||||
@@ -4942,6 +4942,11 @@ static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *f
|
||||
*flags |= H2_SF_HEADERS_RCVD;
|
||||
|
||||
if (h2c->dff & H2_F_HEADERS_END_STREAM) {
|
||||
+ if (msgf & H2_MSGF_RSP_1XX) {
|
||||
+ /* RFC9113#8.1 : HEADERS frame with the ES flag set that carries an informational status code is malformed */
|
||||
+ TRACE_STATE("invalid interim response with ES flag!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
|
||||
+ goto fail;
|
||||
+ }
|
||||
/* no more data are expected for this message */
|
||||
htx->flags |= HTX_FL_EOM;
|
||||
}
|
||||
--
|
||||
2.37.3
|
||||
|
@ -0,0 +1,164 @@
|
||||
From 486cd730485c8a119ef65b3f792134b56e7941b4 Mon Sep 17 00:00:00 2001
|
||||
From: Willy Tarreau <w@1wt.eu>
|
||||
Date: Thu, 9 Feb 2023 21:36:54 +0100
|
||||
Subject: [PATCH] BUG/CRITICAL: http: properly reject empty http header field
|
||||
names
|
||||
|
||||
The HTTP header parsers surprizingly accepts empty header field names,
|
||||
and this is a leftover from the original code that was agnostic to this.
|
||||
|
||||
When muxes were introduced, for H2 first, the HPACK decompressor needed
|
||||
to feed headers lists, and since empty header names were strictly
|
||||
forbidden by the protocol, the lists of headers were purposely designed
|
||||
to be terminated by an empty header field name (a principle that is
|
||||
similar to H1's empty line termination). This principle was preserved
|
||||
and generalized to other protocols migrated to muxes (H1/FCGI/H3 etc)
|
||||
without anyone ever noticing that the H1 parser was still able to deliver
|
||||
empty header field names to this list. In addition to this it turns out
|
||||
that the HPACK decompressor, despite a comment in the code, may
|
||||
successfully decompress an empty header field name, and this mistake
|
||||
was propagated to the QPACK decompressor as well.
|
||||
|
||||
The impact is that an empty header field name may be used to truncate
|
||||
the list of headers and thus make some headers disappear. While for
|
||||
H2/H3 the impact is limited as haproxy sees a request with missing
|
||||
headers, and headers are not used to delimit messages, in the case of
|
||||
HTTP/1, the impact is significant because the presence (and sometimes
|
||||
contents) of certain sensitive headers is detected during the parsing.
|
||||
Thus, some of these headers may be seen, marked as present, their value
|
||||
extracted, but never delivered to upper layers and obviously not
|
||||
forwarded to the other side either. This can have for consequence that
|
||||
certain important header fields such as Connection, Upgrade, Host,
|
||||
Content-length, Transfer-Encoding etc are possibly seen as different
|
||||
between what haproxy uses to parse/forward/route and what is observed
|
||||
in http-request rules and of course, forwarded. One direct consequence
|
||||
is that it is possible to exploit this property in HTTP/1 to make
|
||||
affected versions of haproxy forward more data than is advertised on
|
||||
the other side, and bypass some access controls or routing rules by
|
||||
crafting extraneous requests. Note, however, that responses to such
|
||||
requests will normally not be passed back to the client, but this can
|
||||
still cause some harm.
|
||||
|
||||
This specific risk can be mostly worked around in configuration using
|
||||
the following rule that will rely on the bug's impact to precisely
|
||||
detect the inconsistency between the known body size and the one
|
||||
expected to be advertised to the server (the rule works from 2.0 to
|
||||
2.8-dev):
|
||||
|
||||
http-request deny if { fc_http_major 1 } !{ req.body_size 0 } !{ req.hdr(content-length) -m found } !{ req.hdr(transfer-encoding) -m found } !{ method CONNECT }
|
||||
|
||||
This will exclusively block such carefully crafted requests delivered
|
||||
over HTTP/1. HTTP/2 and HTTP/3 do not need content-length, and a body
|
||||
that arrives without being announced with a content-length will be
|
||||
forwarded using transfer-encoding, hence will not cause discrepancies.
|
||||
In HAProxy 2.0 in legacy mode ("no option http-use-htx"), this rule will
|
||||
simply have no effect but will not cause trouble either.
|
||||
|
||||
A clean solution would consist in modifying the loops iterating over
|
||||
these headers lists to check the header name's pointer instead of its
|
||||
length (since both are zero at the end of the list), but this requires
|
||||
to touch tens of places and it's very easy to miss one. Functions such
|
||||
as htx_add_header(), htx_add_trailer(), htx_add_all_headers() would be
|
||||
good starting points for such a possible future change.
|
||||
|
||||
Instead the current fix focuses on blocking empty headers where they
|
||||
are first inserted, hence in the H1/HPACK/QPACK decoders. One benefit
|
||||
of the current solution (for H1) is that it allows "show errors" to
|
||||
report a precise diagnostic when facing such invalid HTTP/1 requests,
|
||||
with the exact location of the problem and the originating address:
|
||||
|
||||
$ printf "GET / HTTP/1.1\r\nHost: localhost\r\n:empty header\r\n\r\n" | nc 0 8001
|
||||
HTTP/1.1 400 Bad request
|
||||
Content-length: 90
|
||||
Cache-Control: no-cache
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
<html><body><h1>400 Bad request</h1>
|
||||
Your browser sent an invalid request.
|
||||
</body></html>
|
||||
|
||||
$ socat /var/run/haproxy.stat <<< "show errors"
|
||||
Total events captured on [10/Feb/2023:16:29:37.530] : 1
|
||||
|
||||
[10/Feb/2023:16:29:34.155] frontend decrypt (#2): invalid request
|
||||
backend <NONE> (#-1), server <NONE> (#-1), event #0, src 127.0.0.1:31092
|
||||
buffer starts at 0 (including 0 out), 16334 free,
|
||||
len 50, wraps at 16336, error at position 33
|
||||
H1 connection flags 0x00000000, H1 stream flags 0x00000810
|
||||
H1 msg state MSG_HDR_NAME(17), H1 msg flags 0x00001410
|
||||
H1 chunk len 0 bytes, H1 body len 0 bytes :
|
||||
|
||||
00000 GET / HTTP/1.1\r\n
|
||||
00016 Host: localhost\r\n
|
||||
00033 :empty header\r\n
|
||||
00048 \r\n
|
||||
|
||||
I want to address sincere and warm thanks for their great work to the
|
||||
team composed of the following security researchers who found the issue
|
||||
together and reported it: Bahruz Jabiyev, Anthony Gavazzi, and Engin
|
||||
Kirda from Northeastern University, Kaan Onarlioglu from Akamai
|
||||
Technologies, Adi Peleg and Harvey Tuch from Google. And kudos to Amaury
|
||||
Denoyelle from HAProxy Technologies for spotting that the HPACK and
|
||||
QPACK decoders would let this pass despite the comment explicitly
|
||||
saying otherwise.
|
||||
|
||||
This fix must be backported as far as 2.0. The QPACK changes can be
|
||||
dropped before 2.6. In 2.0 there is also the equivalent code for legacy
|
||||
mode, which doesn't suffer from the list truncation, but it would better
|
||||
be fixed regardless.
|
||||
|
||||
CVE-2023-25725 was assigned to this issue.
|
||||
|
||||
(cherry picked from commit a8598a2eb11b6c989e81f0dbf10be361782e8d32)
|
||||
Signed-off-by: Willy Tarreau <w@1wt.eu>
|
||||
(cherry picked from commit a0e561ad7f29ed50c473f5a9da664267b60d1112)
|
||||
Signed-off-by: Willy Tarreau <w@1wt.eu>
|
||||
(cherry picked from commit 73be199c4f5f1ed468161a4c5e10ca77cd5989d8)
|
||||
[wt: dropped QPACK changes for 2.5]
|
||||
Signed-off-by: Willy Tarreau <w@1wt.eu>
|
||||
(cherry picked from commit f8b2b88aeae15dc3b261cd3749277ae75caf9db8)
|
||||
Signed-off-by: Willy Tarreau <w@1wt.eu>
|
||||
---
|
||||
src/h1.c | 4 ++++
|
||||
src/hpack-dec.c | 9 +++++++++
|
||||
2 files changed, 13 insertions(+)
|
||||
|
||||
diff --git a/src/h1.c b/src/h1.c
|
||||
index 4c2e234c5..73de48be0 100644
|
||||
--- a/src/h1.c
|
||||
+++ b/src/h1.c
|
||||
@@ -750,6 +750,10 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
|
||||
|
||||
if (likely(*ptr == ':')) {
|
||||
col = ptr - start;
|
||||
+ if (col <= sol) {
|
||||
+ state = H1_MSG_HDR_NAME;
|
||||
+ goto http_msg_invalid;
|
||||
+ }
|
||||
EAT_AND_JUMP_OR_RETURN(ptr, end, http_msg_hdr_l1_sp, http_msg_ood, state, H1_MSG_HDR_L1_SP);
|
||||
}
|
||||
|
||||
diff --git a/src/hpack-dec.c b/src/hpack-dec.c
|
||||
index 04f3d9ffa..ed39007d1 100644
|
||||
--- a/src/hpack-dec.c
|
||||
+++ b/src/hpack-dec.c
|
||||
@@ -420,6 +420,15 @@ int hpack_decode_frame(struct hpack_dht *dht, const uint8_t *raw, uint32_t len,
|
||||
/* <name> and <value> are correctly filled here */
|
||||
}
|
||||
|
||||
+ /* We must not accept empty header names (forbidden by the spec and used
|
||||
+ * as a list termination).
|
||||
+ */
|
||||
+ if (!name.len) {
|
||||
+ hpack_debug_printf("##ERR@%d##\n", __LINE__);
|
||||
+ ret = -HPACK_ERR_INVALID_ARGUMENT;
|
||||
+ goto leave;
|
||||
+ }
|
||||
+
|
||||
/* here's what we have here :
|
||||
* - name.len > 0
|
||||
* - value is filled with either const data or data allocated from tmp
|
||||
--
|
||||
2.37.3
|
||||
|
Loading…
Reference in new issue