]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: http/hardening: add "delete-unregistered-header-fields"
authorWilly Tarreau <w@1wt.eu>
Tue, 27 Oct 2026 15:29:23 +0000 (16:29 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 4 Nov 2026 21:45:32 +0000 (22:45 +0100)
Deletes from requests and responses any header field whose name starts
with 'x-' (upper or lower case). These header 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, as well as in old browsers that
were trying to work around known server bugs (e.G. "X-Pad"). While it
would be risky to block such requests, it is safe and even recommended
to delete such headers from inbound internet requests. It is preferable
not to do it on responses however when applications uses such header
fields to report information to HAProxy.

doc/configuration.txt
include/haproxy/http-t.h
src/h1.c
src/h2.c
src/h3.c
src/http.c

index 88384bb60ee87df306e6dfe04575c707913b0104..8058187927b1f621ee3f4d85fd5adb69bbe53148 100644 (file)
@@ -34546,6 +34546,18 @@ http-hardening <name> [from <name2>]
         recommended to enable on untrusted listeners. Note that this applies
         only after "reject-underscore-in-trailer-name" was accepted.
 
+  - delete-unregistered-header-fields (inbound, request and response)
+        Deletes from requests and responses any header field whose name starts
+        with 'x-' (upper or lower case). These header 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, as well as in old browsers that
+        were trying to work around known server bugs (e.G. "X-Pad"). While it
+        would be risky to block such requests, it is safe and even recommended
+        to delete such headers from inbound internet requests. It is preferable
+        not to do it on responses however when applications uses such header
+        fields to report information to HAProxy.
+
   - 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
index 39b83c771b15801de952fc19afd47750d0ec5809..a88367aad110cd8cb0d7fba9853358f9124bbfe4 100644 (file)
@@ -246,6 +246,7 @@ struct http_violation {
 #define HTTP_HRD_REJ_USCORE_TLR_NAM         0x00000008
 #define HTTP_HRD_DEL_USCORE_HDR_NAM         0x00000010
 #define HTTP_HRD_DEL_USCORE_TLR_NAM         0x00000020
+#define HTTP_HRD_DEL_UNREG_HDR              0x00000040
 /* insert new masks above this line */
 #define HTTP_HRD_ALL                        0xFFFFFFFF
 
index fa5b0c6bf37435e0213402335ab467e317ac2c77..7fc979db488cced311dc6834d36764e039aa37ea 100644 (file)
--- a/src/h1.c
+++ b/src/h1.c
@@ -1052,6 +1052,13 @@ int h1_headers_to_hdr_list(char *start, const char *stop,
                                        break;
                                }
 
+                               /* we may have to delete unregistered header names (X-) */
+                               if (unlikely(h1m->state != H1_MSG_TRAILERS && http_harden(HTTP_HRD_DEL_UNREG_HDR))) {
+                                       if (n.len > 1 && n.ptr[1] == '-' &&
+                                           (n.ptr[0] == 'x' || n.ptr[0] == 'X'))
+                                               break;
+                               }
+
                                if (isteqi(n, ist("transfer-encoding"))) {
                                        ret = h1_parse_xfer_enc_header(h1m, v);
                                        if (ret < 0) {
index db6a689fd4efd0a0b7e4bdd24c7908f7e7dbce69..cf911302f59ce706e2be20c4ca1a3a234bb0993d 100644 (file)
--- a/src/h2.c
+++ b/src/h2.c
@@ -405,6 +405,14 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms
                        continue;
                }
 
+               /* we may have to delete unregistered header names (X-) */
+               if (unlikely(http_harden(HTTP_HRD_DEL_UNREG_HDR))) {
+                       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;
+               }
+
                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);
@@ -741,6 +749,14 @@ int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *m
                        continue;
                }
 
+               /* we may have to delete unregistered header names (X-) */
+               if (unlikely(http_harden(HTTP_HRD_DEL_UNREG_HDR))) {
+                       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;
+               }
+
                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);
index ee3598af76faf17db0b07c37be28da5b6aa98d52..226d1a8b416c73d4939b9eb3170694cca7ca5d00 100644 (file)
--- a/src/h3.c
+++ b/src/h3.c
@@ -1071,6 +1071,16 @@ static ssize_t h3_req_headers_to_htx(struct qcs *qcs, const struct buffer *buf,
                        continue;
                }
 
+               /* we may have to delete unregistered header names (X-) */
+               if (unlikely(http_harden(HTTP_HRD_DEL_UNREG_HDR))) {
+                       if (list[hdr_idx].n.len > 1 && list[hdr_idx].n.ptr[1] == '-' &&
+                           (list[hdr_idx].n.ptr[0] == 'x' || list[hdr_idx].n.ptr[0] == 'X')) {
+                               /* skip it, so it won't be added */
+                               ++hdr_idx;
+                               continue;
+                       }
+               }
+
                if (isteq(list[hdr_idx].n, ist("host"))) {
                        struct ist prev_auth = authority;
 
index 18f0eb9a7bc671527135d5ead8fa84547d2c2716..94a19a0364ed187cd9ef16124fae820124774880 100644 (file)
@@ -1781,6 +1781,7 @@ const struct http_hardening_kw http_hardening_kws[] = {
        { "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 },
        { "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 },
        /* insert new hardening options above this line */
        { "all",                            HTTP_HRD_ALL,              HTTP_DIR_BOTH },
        { NULL, 0 }