From: Willy Tarreau Date: Tue, 27 Oct 2026 14:16:23 +0000 (+0100) Subject: MINOR: http/hardening: implement hardening "reject-lowercase-method" X-Git-Tag: v3.5-dev11~21 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=b3b0b0ac7f87a1edf25448c9afb8df47637a8311;p=haproxy.git MINOR: http/hardening: implement hardening "reject-lowercase-method" Rejects requests whose method contains a lower-case character. No standard HTTP method uses lower-case characters and these ones are essentially used in desynchronization attack scenarios, by trying to exploit different case matching schemes along the chain. This is recommended on untrusted listeners. This is implemented directly in the h1 parser, as well as in http_method_has_forbidden_char() for h2 and h3. H2 now uses this function instead of rolling its own loop. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 0a2fc19ae..b27da2393 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -34531,6 +34531,13 @@ http-hardening [from ] even if not rejected, duplicate host header fields are folded into a single one. + - reject-lowercase-method (inbound request) + Rejects requests whose method contains a lower-case character. No + standard HTTP method uses lower-case characters and these ones are + essentially used in desynchronization attack scenarios, by trying to + exploit different case matching schemes along the chain. This is + recommended on untrusted listeners. + /* * Local variables: * fill-column: 79 diff --git a/include/haproxy/http-t.h b/include/haproxy/http-t.h index 5fb443c02..1d7ec2598 100644 --- a/include/haproxy/http-t.h +++ b/include/haproxy/http-t.h @@ -241,6 +241,7 @@ struct http_violation { #define HTTP_HRD_NONE 0x00000000 /* insert new masks below this line */ #define HTTP_HRD_REJ_DUP_HOST_HDR 0x00000001 +#define HTTP_HRD_REJ_LCASE_METHOD 0x00000002 /* insert new masks above this line */ #define HTTP_HRD_ALL 0xFFFFFFFF diff --git a/include/haproxy/http.h b/include/haproxy/http.h index 33b73df00..2f70a7719 100644 --- a/include/haproxy/http.h +++ b/include/haproxy/http.h @@ -235,8 +235,8 @@ static inline int http_header_has_forbidden_char(const struct ist ist, const cha return 0; } -/* Check that method only contains token as required. - * See RFC 9110 9. Methods +/* Check that method only contains token as required. See RFC 9110 9. Methods. + * Can also enforce non-lower case if hardening requires it. */ static inline int http_method_has_forbidden_char(const struct ist ist) { @@ -245,6 +245,11 @@ static inline int http_method_has_forbidden_char(const struct ist ist) do { if (!HTTP_IS_TOKEN(*start)) return 1; + + if (unlikely(islower((unsigned char)*start))) { + if (http_harden(HTTP_HRD_REJ_LCASE_METHOD)) + return 1; + } start++; } while (start < istend(ist)); return 0; diff --git a/src/h1.c b/src/h1.c index 3d3e40068..5ed858247 100644 --- a/src/h1.c +++ b/src/h1.c @@ -509,8 +509,15 @@ int h1_headers_to_hdr_list(char *start, const char *stop, case H1_MSG_RQMETH: http_msg_rqmeth: - if (likely(HTTP_IS_TOKEN(*ptr))) + if (likely(HTTP_IS_TOKEN(*ptr))) { + if (unlikely(islower((unsigned char)*ptr))) { + if (http_harden(HTTP_HRD_REJ_LCASE_METHOD)) { + state = H1_MSG_RQMETH; + goto http_msg_invalid; + } + } EAT_AND_JUMP_OR_RETURN(ptr, end, http_msg_rqmeth, http_msg_ood, state, H1_MSG_RQMETH); + } if (likely(HTTP_IS_SPHT(*ptr))) { sl.rq.m.len = ptr - sl.rq.m.ptr; diff --git a/src/h2.c b/src/h2.c index d67f363fc..031db21e5 100644 --- a/src/h2.c +++ b/src/h2.c @@ -255,12 +255,8 @@ static struct htx_sl *h2_prepare_htx_reqline(uint32_t fields, struct ist *phdr, } /* The method is a non-empty token (RFC7231#4.1) */ - if (!meth_sl.len) - goto fail; - for (i = 0; i < meth_sl.len; i++) { - if (!HTTP_IS_TOKEN(meth_sl.ptr[i])) - htx->flags |= HTX_FL_PARSING_ERROR; - } + if (!istlen(meth_sl) || http_method_has_forbidden_char(meth_sl)) + htx->flags |= HTX_FL_PARSING_ERROR; /* make sure the final URI isn't empty. Note that 7540#8.1.2.3 states * that :path must not be empty. diff --git a/src/http.c b/src/http.c index 3113c4327..449407003 100644 --- a/src/http.c +++ b/src/http.c @@ -1776,6 +1776,7 @@ static int http_parse_acceptable_http_violations(char **args, int section_type, const struct http_hardening_kw http_hardening_kws[] = { /* insert new hardening options below this line */ { "reject-duplicate-host-header", HTTP_HRD_REJ_DUP_HOST_HDR, HTTP_DIR_REQ }, + { "reject-lowercase-method", HTTP_HRD_REJ_LCASE_METHOD, HTTP_DIR_REQ }, /* insert new hardening options above this line */ { "all", HTTP_HRD_ALL, HTTP_DIR_BOTH }, { NULL, 0 }