From: Willy Tarreau Date: Tue, 27 Oct 2026 14:53:06 +0000 (+0100) Subject: MINOR: http/harderning: implement "delete-underscore-in-header-name" X-Git-Tag: v3.5-dev11~18 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=d65f117160cc62b5d0fd92ae50964934ebcbf3d6;p=haproxy.git MINOR: http/harderning: implement "delete-underscore-in-header-name" Deletes from requests and responses any header field whose name contains an underscore character ('_' or \x5C). While this character is perfectly permitted, some application frameworks which create automatic variables already turn the dash ('-') into an underscore in variable names, leading to an ambiguity with the underscore which can result in the same variable name. This could be abused to bypass some access-control policy for example. No standard HTTP header uses this, so it is recommended to enable on untrusted listeners. Note that this applies only after "reject-underscore-in-header-name" was accepted. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index ab698debb..ef5800df4 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -34525,6 +34525,17 @@ http-hardening [from ] This keyword covers all known keywords together. It is essentially used to enable or disable all checks at once. + - delete-underscore-in-header-name (inbound, request and response) + Deletes from requests and responses any header field whose name + contains an underscore character ('_' or \x5C). While this character is + perfectly permitted, some application frameworks which create automatic + variables already turn the dash ('-') into an underscore in variable + names, leading to an ambiguity with the underscore which can result in + the same variable name. This could be abused to bypass some + access-control policy for example. No standard HTTP header uses this, + so it is recommended to enable on untrusted listeners. Note that this + applies only after "reject-underscore-in-header-name" was accepted. + - reject-duplicate-host-header (inbound request) Rejects requests containing more than one "Host" header field, even if valid and identical. This is recommended on untrusted listeners. Note that @@ -34546,7 +34557,8 @@ http-hardening [from ] variable names, leading to an ambiguity with the underscore which can result in the same variable name. This could be abused to bypass some access-control policy for example. No standard HTTP header uses this, - so it is safe to enable on untrusted listeners. + so it is safe to enable on untrusted listeners. See also the + "delete-underscore-in-header-name" variant. - reject-underscore-in-trailer-name (inbound, request and response) Rejects requests and responses in which one tailer field name contains diff --git a/include/haproxy/http-t.h b/include/haproxy/http-t.h index efe0bd340..baf99e9ea 100644 --- a/include/haproxy/http-t.h +++ b/include/haproxy/http-t.h @@ -244,6 +244,7 @@ struct http_violation { #define HTTP_HRD_REJ_LCASE_METHOD 0x00000002 #define HTTP_HRD_REJ_USCORE_HDR_NAM 0x00000004 #define HTTP_HRD_REJ_USCORE_TLR_NAM 0x00000008 +#define HTTP_HRD_DEL_USCORE_HDR_NAM 0x00000010 /* insert new masks above this line */ #define HTTP_HRD_ALL 0xFFFFFFFF diff --git a/src/h1.c b/src/h1.c index c6c9fccbf..459e4cd20 100644 --- a/src/h1.c +++ b/src/h1.c @@ -1038,6 +1038,18 @@ int h1_headers_to_hdr_list(char *start, const char *stop, break; } + /* we may have to delete headers with underscores + * in their names. These ones are already marked + * as found when not rejected so we can avoid a + * costly lookup in normal times. + */ + if (unlikely(http_hdng_chk.found & HTTP_HRD_REJ_USCORE_HDR_NAM) && + ((h1m->state != H1_MSG_TRAILERS && istlen(istfind(n, '_')) && + http_harden(HTTP_HRD_DEL_USCORE_HDR_NAM)))) { + /* skip it, so it won't be added */ + break; + } + if (isteqi(n, ist("transfer-encoding"))) { ret = h1_parse_xfer_enc_header(h1m, v); if (ret < 0) { diff --git a/src/h2.c b/src/h2.c index fa9486bdb..db6a689fd 100644 --- a/src/h2.c +++ b/src/h2.c @@ -394,6 +394,17 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms } /* regular header field in (name,value) */ + + /* we may have to delete headers with underscores in their + * names. These ones are already marked as found when not + * rejected so we can avoid a costly lookup in normal times. + */ + if (unlikely(http_hdng_chk.found & HTTP_HRD_REJ_USCORE_HDR_NAM) && + istlen(istfind(list[idx].n, '_')) && http_harden(HTTP_HRD_DEL_USCORE_HDR_NAM)) { + /* skip it, so it won't be added */ + continue; + } + if (unlikely(!(fields & H2_PHDR_FND_NONE))) { /* no more pseudo-headers, time to build the request line */ sl = h2_prepare_htx_reqline(fields, phdr_val, htx, msgf); @@ -719,6 +730,17 @@ int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *m } /* regular header field in (name,value) */ + + /* we may have to delete headers with underscores in their + * names. These ones are already marked as found when not + * rejected so we can avoid a costly lookup in normal times. + */ + if (unlikely(http_hdng_chk.found & HTTP_HRD_REJ_USCORE_HDR_NAM) && + istlen(istfind(list[idx].n, '_')) && http_harden(HTTP_HRD_DEL_USCORE_HDR_NAM)) { + /* skip it, so it won't be added */ + continue; + } + if (!(fields & H2_PHDR_FND_NONE)) { /* no more pseudo-headers, time to build the status line */ sl = h2_prepare_htx_stsline(fields, phdr_val, htx, msgf); diff --git a/src/h3.c b/src/h3.c index 3fbfa2758..ee3598af7 100644 --- a/src/h3.c +++ b/src/h3.c @@ -1060,6 +1060,17 @@ static ssize_t h3_req_headers_to_htx(struct qcs *qcs, const struct buffer *buf, goto out; } + /* we may have to delete headers with underscores in their + * names. These ones are already marked as found when not + * rejected so we can avoid a costly lookup in normal times. + */ + if (unlikely(http_hdng_chk.found & HTTP_HRD_REJ_USCORE_HDR_NAM) && + istlen(istfind(list[hdr_idx].n, '_')) && http_harden(HTTP_HRD_DEL_USCORE_HDR_NAM)) { + /* skip it, so it won't be added */ + ++hdr_idx; + continue; + } + if (isteq(list[hdr_idx].n, ist("host"))) { struct ist prev_auth = authority; diff --git a/src/http.c b/src/http.c index 0ef537667..341dee01f 100644 --- a/src/http.c +++ b/src/http.c @@ -1779,6 +1779,7 @@ const struct http_hardening_kw http_hardening_kws[] = { { "reject-lowercase-method", HTTP_HRD_REJ_LCASE_METHOD, HTTP_DIR_REQ }, { "reject-underscore-in-header-name", HTTP_HRD_REJ_USCORE_HDR_NAM, HTTP_DIR_BOTH }, { "reject-underscore-in-trailer-name", HTTP_HRD_REJ_USCORE_TLR_NAM, HTTP_DIR_BOTH }, + { "delete-underscore-in-header-name", HTTP_HRD_DEL_USCORE_HDR_NAM, HTTP_DIR_BOTH }, /* insert new hardening options above this line */ { "all", HTTP_HRD_ALL, HTTP_DIR_BOTH }, { NULL, 0 }