From b5fd464f0a3eeea11c539a12d9a53ff3914a33ec Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Tue, 3 Nov 2026 16:25:49 +0100 Subject: [PATCH] MINOR: http/hardening: add "delete-unregistered-trailer-fields" This is the same as for the headers, but this time for the trailers. Better keep them separated as trailers are used even worse than headers these days. Similarly as for the underscore, unwanted trailers are dropped early for h1. --- doc/configuration.txt | 9 +++++++++ include/haproxy/http-t.h | 1 + src/h1.c | 3 ++- src/http.c | 1 + src/http_htx.c | 8 ++++++++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 805818792..acb31bd6d 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -34558,6 +34558,15 @@ http-hardening [from ] not to do it on responses however when applications uses such header fields to report information to HAProxy. + - delete-unregistered-trailer-fields (inbound, request and response) + Deletes from requests and responses any trailer field whose name starts + with 'x-' (upper or lower case). These trailer fields are not + registered with IANA and are not used across the internet by standard + components. They may appear in untrusted requests in attacks trying to + exploit some backend framework weaknesses however, so it is generally + safe and even recommended to delete such trailers from inbound internet + requests. + - 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 diff --git a/include/haproxy/http-t.h b/include/haproxy/http-t.h index a88367aad..e5019bb3d 100644 --- a/include/haproxy/http-t.h +++ b/include/haproxy/http-t.h @@ -247,6 +247,7 @@ struct http_violation { #define HTTP_HRD_DEL_USCORE_HDR_NAM 0x00000010 #define HTTP_HRD_DEL_USCORE_TLR_NAM 0x00000020 #define HTTP_HRD_DEL_UNREG_HDR 0x00000040 +#define HTTP_HRD_DEL_UNREG_TLR 0x00000080 /* insert new masks above this line */ #define HTTP_HRD_ALL 0xFFFFFFFF diff --git a/src/h1.c b/src/h1.c index 7fc979db4..052e49e26 100644 --- a/src/h1.c +++ b/src/h1.c @@ -1053,7 +1053,8 @@ int h1_headers_to_hdr_list(char *start, const char *stop, } /* we may have to delete unregistered header names (X-) */ - if (unlikely(h1m->state != H1_MSG_TRAILERS && http_harden(HTTP_HRD_DEL_UNREG_HDR))) { + if (unlikely((h1m->state != H1_MSG_TRAILERS && http_harden(HTTP_HRD_DEL_UNREG_HDR)) || + (h1m->state == H1_MSG_TRAILERS && http_harden(HTTP_HRD_DEL_UNREG_TLR)))) { if (n.len > 1 && n.ptr[1] == '-' && (n.ptr[0] == 'x' || n.ptr[0] == 'X')) break; diff --git a/src/http.c b/src/http.c index 94a19a036..a4df4e177 100644 --- a/src/http.c +++ b/src/http.c @@ -1782,6 +1782,7 @@ const struct http_hardening_kw http_hardening_kws[] = { { "delete-underscore-in-header-name", HTTP_HRD_DEL_USCORE_HDR_NAM, HTTP_DIR_BOTH }, { "delete-underscore-in-trailer-name", HTTP_HRD_DEL_USCORE_TLR_NAM, HTTP_DIR_BOTH }, { "delete-unregistered-header-fields",HTTP_HRD_DEL_UNREG_HDR, HTTP_DIR_BOTH }, + { "delete-unregistered-trailer-fields",HTTP_HRD_DEL_UNREG_TLR, HTTP_DIR_BOTH }, /* insert new hardening options above this line */ { "all", HTTP_HRD_ALL, HTTP_DIR_BOTH }, { NULL, 0 } diff --git a/src/http_htx.c b/src/http_htx.c index b465b218b..39f4cb352 100644 --- a/src/http_htx.c +++ b/src/http_htx.c @@ -2085,6 +2085,14 @@ enum http_parser_status http_trailers_to_htx(struct http_hdr *list, struct htx * continue; } + /* we may have to delete unregistered header names (X-) */ + if (unlikely(http_harden(HTTP_HRD_DEL_UNREG_TLR))) { + if (list[idx].n.len > 1 && list[idx].n.ptr[1] == '-' && + (list[idx].n.ptr[0] == 'x' || list[idx].n.ptr[0] == 'X')) + /* skip it, so it won't be added */ + continue; + } + /* trim leading/trailing LWS as per RC9113#8.2.1 */ for (v = list[idx].v; v.len; v.len--) { if (unlikely(HTTP_IS_LWS(*v.ptr))) -- 2.47.3