From: Willy Tarreau Date: Wed, 11 Nov 2026 12:36:57 +0000 (+0000) Subject: DOC: configuration: add a section on secure filtering of HTTP requests X-Git-Tag: v3.5-dev12~15 X-Git-Url: https://git.kaiwu.me/%7B@url%7D?a=commitdiff_plain;h=742af3e70bb6781c831728dd40378db780ed8229;p=haproxy.git DOC: configuration: add a section on secure filtering of HTTP requests Deny rules matching a path, an URL or a query string are regularly reported as bypassable using alternate encodings or aliases (case, repeated slashes, "." and ".." segments, %-encoding, "%u" encoding, backslashes, path parameters etc). This is expected: HTTP has many aliases and which ones apply depends on what is behind haproxy, so no normalization performed here can be correct in the general case. The only reliable approach is an allow-list: accept what is explicitly recognized and reject everything else. Let's document this in a new section 7.5, starting with a table of aliases of a trivially denied path to make the problem obvious, explain why it cannot be solved by code, show how to write allow-list rules with "deny unless" or with a variable set by each accepting rule and checked once at the end, and list the patterns worth spotting when reviewing a configuration. Link: https://www.mail-archive.com/haproxy@formilux.org/msg37732.html --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 418379ef7..d9c41bf00 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -95,6 +95,7 @@ Summary 7.3.6. Fetching HTTP samples (Layer 7) 7.3.7. Fetching samples for developers 7.4. Pre-defined ACLs +7.5. Secure filtering of HTTP requests 8. Logging 8.1. Log levels @@ -16664,7 +16665,9 @@ normalize-uri query-sort-by-name Usable in: QUIC Ini| TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft - | - | - | - | - | X | - | - - Performs normalization of the request's URI. + Performs normalization of the request's URI. Please read section 7.5, "Secure + filtering of HTTP requests" before engaging into request normalization if the + goal is to perform access control. URI normalization is available as an experimental technical preview since HAProxy 2.4. As such, it requires the global directive @@ -29702,6 +29705,144 @@ WAIT_END wait_end wait for end of content anal ---------------+----------------------------------+------------------------------------------------------ +7.5. Secure filtering of HTTP requests +-------------------------------------- + +This section explains how to write secure filtering rules without falling into +some of the common traps caused by the way various HTTP implementations will +decode a request. From time to time some users are surprised to learn that a +rule such as: + + http-request deny if { path_beg /admin } + +can be bypassed by playing with various encoding, and ask why haproxy doesn't +perform itself a form of decoding or normalization before processing the rule. +The reason is that many forms exist and might be decoded differently by +different server implementations, as not all will normalize everything exactly +the same way. Please consider the variants below, some of which might target a +different resource on some servers, or might alias to /admin on other servers: + + Request path Why it may reach the same resource + --------------------+--------------------------------------------------- + /Admin case-insensitive file systems or applications + /admin/ trailing slash (when an exact match was used) + //admin repeated slashes are merged + /./admin "." segment is dropped + /x/../admin ".." segment is resolved + /admin//../ when ".." is resolved before "//", it gives "/admin/" + /admin//a/../ when ".." is resolved after "//", it gives "/admin/" + /%61dmin %-encoded character, decoded by the server + /%2e/admin %-encoded "." segment, decoded then dropped + /%u0061dmin non-standard "%u" encoding, supported by some servers + /\admin "\" used as a directory delimiter + /%5Cadmin same as "\", but %-encoded + /;x=1/admin path parameters, dropped by some frameworks + /admin%00.png truncation at an encoded NUL by some servers + /admin%3ffoo %3f turned to '?' by some proxies, splitting the path + /%2561dmin %-encoded '%' before 'a', resolved by some proxies + --------------------+--------------------------------------------------- + +The possibilities are numerous, and given that different servers will see these +as different resources, there's no universal way to decode and match such a +request. This implies that the rule above does NOT protect against various +possibilities to access "/admin", and that no rule will ever do that. + +This is not a bug in the matching, and no option of the matching can solve it. +HTTP is full of aliases, and which ones apply depends on what is behind +HAProxy: the operating system and its file system, the web server, the +framework, the application, a cache, another reverse-proxy, and even their +respective versions or configurations. HAProxy cannot know that, and cannot +guess which transformation will be applied after it, nor in which order (e.g. +"//" before or after ".."). Except for the few cases where the administrator +perfectly knows the application server and can carefully craft some rules for +"normalize-uri", it cannot be solved by rewriting the request in HAProxy +either, because the only valid normalization is the one done by the component +which will interpret the request, and any other one will either leave some +aliases or break legitimate requests (browsers, for example, do not normalize +what they send, and some applications legitimately rely on encoded delimiters). +Worse, blindly doing normalization without a precise understanding of how the +server parses a request could even help an attacker abuse the rewrite performed +at this normalization stage to hide a request from a front reverse-proxy. This +limitation applies to every component in a chain, and not just to HAProxy. + +The same is true for the query string, which supports encoding as well : "+" +and "%20" are equivalent for some applications and not for others (and some +even resolve it in the path), and "