Description
Detailed Description of the Problem
If I send an HTTP/2 request to HAProxy with a :authority
pseudo-header that contains /
, HAProxy prepends the part including and after the first /
to the :path
pseudo-header. If an upstream proxy performs path-based access control but does not validate :authority
, these checks could be bypassed.
Expected Behavior
HAProxy should reject the request because /
is not allowed in :authority
pseudo-headers.
Steps to Reproduce the Behavior
- Send an HTTP/2 request to HAProxy with
:authority: a/b
. - Log what the upstream server receives.
Do you have any idea what may have caused this?
HAProxy forms a request URI by string concatenation, then re-parses it to determine the path to send upstream for HTTP/1.x requests. This is only safe if parsing the URI will result in the same path that was used to form the URI, but in this case it does not.
Do you have an idea how to solve the issue?
HAProxy should strictly validate the :authority
and Host
headers. In particular, it should reject:
- Userinfo components, which are probably a phishing attempt per https://rfc-editor.org/rfc/rfc9110.html#section-4.2.4
- Invalid port numbers.
- Forbidden or delimiter characters in the non-
[::]
form. - An invalid IPv6 address between
[
and]
.
These rules come from Envoy (https://envoyproxy.io).
What is your configuration?
global
log stderr local0 debug
chroot /run/haproxy
maxconn 4000
limited-quic
user haproxy
group haproxy
stats socket /run/haproxy/haproxy-stats
ssl-default-bind-curves X25519:P-256:P-384
ssl-default-bind-ciphersuites TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options prefer-client-ciphers ssl-min-ver TLSv1.3
unix-bind prefix /run/haproxy user haproxy mode 0660 group qubes
setcap cap_net_bind_service
defaults
log global
timeout client 3000ms
timeout connect 3000ms
timeout server 3000ms
crt-store
load key '/usr/local/etc/haproxy.key' crt /usr/local/etc/haproxy.pem alias 'main'
frontend main
mode http
default_backend app
bind quic4@*:443 ssl crt "@/main" name main_quic_v4
bind quic6@*:443 ssl crt "@/main" name main_quic_v6
bind *:443 ssl crt "@/main" name main_tls
option httplog
http-request deny status 400 unless { path_beg '/test/' }
frontend local_socket
bind /reroute
mode http
default_backend trivial
option httplog
backend app
mode http
server reroute /reroute proto h2
backend trivial
mode http
server simple /launch proto h1
option http-server-close
Output of haproxy -vv
HAProxy version 3.1.7 2025/04/17 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2026.
Known bugs: http://www.haproxy.org/bugs/bugs-3.1.7.html
Running on: Linux 6.12.21-1.qubes.fc37.x86_64 #1 SMP PREEMPT_DYNAMIC Mon Mar 31 11:38:40 GMT 2025 x86_64
Build options :
TARGET = linux-glibc
CC = cc
CFLAGS = -O2 -g -fwrapv
OPTIONS = USE_PTHREAD_EMULATION=0 USE_OPENSSL=1 USE_LUA=1 USE_QUIC=1 USE_PCRE2=1 USE_PCRE2_JIT=1 USE_QUIC_OPENSSL_COMPAT=1
DEBUG =
Feature list : -51DEGREES +ACCEPT4 +BACKTRACE -CLOSEFROM +CPU_AFFINITY +CRYPT_H -DEVICEATLAS +DL -ENGINE +EPOLL -EVPORTS +GETADDRINFO -KQUEUE -LIBATOMIC +LIBCRYPT +LINUX_CAP +LINUX_SPLICE +LINUX_TPROXY +LUA +MATH -MEMORY_PROFILING +NETFILTER +NS -OBSOLETE_LINKER +OPENSSL -OPENSSL_AWSLC -OPENSSL_WOLFSSL -OT -PCRE +PCRE2 +PCRE2_JIT -PCRE_JIT +POLL +PRCTL -PROCCTL -PROMEX -PTHREAD_EMULATION +QUIC +QUIC_OPENSSL_COMPAT +RT +SHM_OPEN +SLZ +SSL -STATIC_PCRE -STATIC_PCRE2 +TFO +THREAD +THREAD_DUMP +TPROXY -WURFL -ZLIB
Default settings :
bufsize = 16384, maxrewrite = 1024, maxpollevents = 200
Built with multi-threading support (MAX_TGROUPS=16, MAX_THREADS=256, default=2).
Built with OpenSSL version : OpenSSL 3.2.4 11 Feb 2025
Running on OpenSSL version : OpenSSL 3.2.4 11 Feb 2025
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
OpenSSL providers loaded : default
Built with Lua version : Lua 5.4.7
Built with network namespace support.
Built with libslz for stateless compression.
Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip")
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Built with PCRE2 version : 10.44 2024-06-07
PCRE2 library supports JIT : yes
Encrypted password support via crypt(3): yes
Built with gcc compiler version 14.2.1 20250110 (Red Hat 14.2.1-7)
Available polling systems :
epoll : pref=300, test result OK
poll : pref=200, test result OK
select : pref=150, test result OK
Total: 3 (3 usable), will use epoll.
Available multiplexer protocols :
(protocols marked as <default> cannot be specified using 'proto' keyword)
quic : mode=HTTP side=FE mux=QUIC flags=HTX|NO_UPG|FRAMED
h2 : mode=HTTP side=FE|BE mux=H2 flags=HTX|HOL_RISK|NO_UPG
<default> : mode=HTTP side=FE|BE mux=H1 flags=HTX
h1 : mode=HTTP side=FE|BE mux=H1 flags=HTX|NO_UPG
fcgi : mode=HTTP side=BE mux=FCGI flags=HTX|HOL_RISK|NO_UPG
<default> : mode=SPOP side=BE mux=SPOP flags=HOL_RISK|NO_UPG
spop : mode=SPOP side=BE mux=SPOP flags=HOL_RISK|NO_UPG
<default> : mode=TCP side=FE|BE mux=PASS flags=
none : mode=TCP side=FE|BE mux=PASS flags=NO_UPG
Available services : none
Available filters :
[BWLIM] bwlim-in
[BWLIM] bwlim-out
[CACHE] cache
[COMP] compression
[FCGI] fcgi-app
[SPOE] spoe
[TRACE] trace
Last Outputs and Backtraces
Additional Information
This should also reproduce with HTTP/3 requests in the development branch. Envoy Proxy has a readable implementation of the above-mentioned validation logic in https://github.com/envoyproxy/envoy/blob/72fd99b8c3681e413eb40ecaebfb1c3454810763/source/extensions/http/header_validators/envoy_default/header_validator.cc, but this is licensed under Apache 2.0 and is in C++.