]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: h1/h2/h3: reject forbidden chars in the Host header field
authorWilly Tarreau <w@1wt.eu>
Fri, 16 May 2025 12:58:52 +0000 (14:58 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 16 May 2025 13:13:17 +0000 (15:13 +0200)
In continuation with 9a05c1f574 ("BUG/MEDIUM: h2/h3: reject some
forbidden chars in :authority before reassembly") and the discussion
in issue #2941, @DemiMarie rightfully suggested that Host should also
be sanitized, because it is sometimes used in concatenation, such as
this:

    http-request set-url https://%[req.hdr(host)]%[pathq]

which was proposed as a workaround for h2 upstream servers that require
:authority here:

    https://www.mail-archive.com/haproxy@formilux.org/msg43261.html

The current patch then adds the same check for forbidden chars in the
Host header, using the same function as for the patch above, since in
both cases we validate the host:port part of the authority. This way
we won't reconstruct ambiguous URIs by concatenating Host and path.

Just like the patch above, this can be backported afer a period of
observation.

src/h1.c
src/h2.c
src/h3.c

index 5708496baa2d96274dc3e0dfa87af8e036dad2ba..1ad5ac19fb6e9a58a24554fd3d8eb09addf9ea69 100644 (file)
--- a/src/h1.c
+++ b/src/h1.c
@@ -986,8 +986,14 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
                                        h1_parse_upgrade_header(h1m, v);
                                }
                                else if (!(h1m->flags & H1_MF_RESP) && isteqi(n, ist("host"))) {
-                                       if (host_idx == -1)
+                                       if (host_idx == -1) {
                                                host_idx = hdr_count;
+                                               if (http_authority_has_forbidden_char(v)) {
+                                                       state = H1_MSG_HDR_L2_LWS;
+                                                       ptr = v.ptr; /* Set ptr on the error */
+                                                       goto http_msg_invalid;
+                                               }
+                                       }
                                        else {
                                                if (!isteqi(v, hdr[host_idx].v)) {
                                                        state = H1_MSG_HDR_L2_LWS;
index 5a8c250688b76b61ae2bb687e851051c893ab8bc..491f62323c25c9eab7131f4513ffe31ff1753eb4 100644 (file)
--- a/src/h2.c
+++ b/src/h2.c
@@ -411,10 +411,13 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms
                }
 
                if (isteq(list[idx].n, ist("host"))) {
+                       /* skip duplicates */
                        if (fields & H2_PHDR_FND_HOST)
                                continue;
 
                        fields |= H2_PHDR_FND_HOST;
+                       if (http_authority_has_forbidden_char(list[idx].v))
+                               goto fail;
                }
 
                if (isteq(list[idx].n, ist("content-length"))) {
index d03c963c5e2f6d510481d130a903c8e1ca033d2f..b2f6750fb57a7b4b0fba25e6a59114826ba1cca8 100644 (file)
--- a/src/h3.c
+++ b/src/h3.c
@@ -863,7 +863,8 @@ static ssize_t h3_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
                if (isteq(list[hdr_idx].n, ist("host"))) {
                        struct ist prev_auth = authority;
 
-                       if (h3_set_authority(qcs, &authority, list[hdr_idx].v)) {
+                       if (http_authority_has_forbidden_char(list[hdr_idx].v) ||
+                           h3_set_authority(qcs, &authority, list[hdr_idx].v)) {
                                h3s->err = H3_ERR_MESSAGE_ERROR;
                                qcc_report_glitch(h3c->qcc, 1);
                                len = -1;