8000 HTTP/3: upgrade header forwarded · Issue #2928 · haproxy/haproxy · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

HTTP/3: upgrade header forwarded #2928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
pittgi opened this issue Apr 11, 2025 · 4 comments
Open

HTTP/3: upgrade header forwarded #2928

pittgi opened this issue Apr 11, 2025 · 4 comments
Labels
status: needs-triage This issue needs to be triaged. type: bug This issue describes a bug.

Comments

@pittgi
Copy link
pittgi commented Apr 11, 2025

Detailed Description of the Problem

An HTTP/3 request containing the upgrade header is forwarded without modification.
The upgrade header is not supported in HTTP/3, as RFC 9114 states:

4.5. HTTP Upgrade
HTTP/3 does not support the HTTP Upgrade mechanism (Section 7.8 of [HTTP]) or the 101 (Switching Protocols) informational status code (Section 15.2.2 of [HTTP]).

This could cause issues, esp. with HTTP/1.1 backend servers.

Expected Behavior

If a client sends an upgrade header in a request, it should be either deleted or the request should be considered as malformed and be rejected, since the upgrade header would influence the backend communication. Rejecting is best practice in my opinion.

Steps to Reproduce the Behavior

  1. Setup an environment where haproxy forwards HTTP/3 requests to an HTTP/1.1 backend.
  2. Send a request containing the ugprade header (curl with HTTP/3 does not support this, use something like aioquic.
  3. Observe traffic using wireshark or let backend print the received request.

Do you have any idea what may have caused this?

No response

Do you have an idea how to solve the issue?

No response

What is your configuration?

frontend http_in
	mode http
	bind :80
	bind :443  ssl crt /PATH/TO/KEY alpn h2
	bind quic4@:443 ssl crt /PATH/TO/KEY alpn h3
	http-request redirect scheme https unless { ssl_fc }
	http-after-response add-header alt-svc 'h3=":443"; ma=60'
	default_backend http_backend

backend http_backend
	server backend1 127.0.0.1:8080

Output of haproxy -vv

HAProxy version 3.1.5-076df02 2025/02/20 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2026.
Known bugs: http://www.haproxy.org/bugs/bugs-3.1.5.html
Running on: Linux 6.11.0-21-generic #21~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Feb 24 16:52:15 UTC 2 x86_64
Build options :
  TARGET  = linux-glibc
  CC      = cc
  CFLAGS  = -O2 -g -fwrapv
  OPTIONS = USE_OPENSSL=1 USE_LUA=1 USE_ZLIB=1 USE_QUIC=1 USE_PROMEX=1 USE_PCRE=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=16).
Built with OpenSSL version : OpenSSL 1.1.1t+quic  7 Feb 2023
Running on OpenSSL version : OpenSSL 1.1.1t+quic  7 Feb 2023
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
Built with Lua version : Lua 5.3.6
Built with the Prometheus exporter as a service
Built with network namespace support.
Built with zlib version : 1.3
Running on zlib version : 1.3
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 PCRE version : 8.39 2016-06-14
Running on PCRE version : 8.39 2016-06-14
PCRE library supports JIT : no (USE_PCRE_JIT not set)
Encrypted password support via crypt(3): yes
Built with gcc compiler version 13.3.0

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 : prometheus-exporter
Available filters :
	[BWLIM] bwlim-in
	[BWLIM] bwlim-out
	[CACHE] cache
	[COMP] compression
	[FCGI] fcgi-app
	[SPOE] spoe
	[TRACE] trace

Last Outputs and Backtraces

00000000:http_in.accept(0004)=7cc0 from [127.0.0.1:51576] ALPN=h3
00000000:http_in.clireq[7cc0:ffffffff]: POST / HTTP/3.0
00000000:http_in.clihdr[7cc0:ffffffff]: host: localhost:443
00000000:http_in.clihdr[7cc0:ffffffff]: upgrade: test
00000000:http_backend.srvrep[7cc0:0038]: HTTP/1.1 200 OK
00000000:http_backend.srvhdr[7cc0:0038]: content-length: 85
00000000:http_backend.srvhdr[7cc0:0038]: content-type: text/plain
00000000:http_backend.srvcls[ffff:0038]
00000000:http_backend.clicls[ffff:0038]
00000000:http_backend.closed[ffff:0038]

Additional Information

No response

@pittgi pittgi added status: needs-triage This issue needs to be triaged. type: bug This issue describes a bug. labels Apr 11, 2025
@DemiMarie
Copy link
DemiMarie commented Apr 13, 2025

Could this result in request smuggling? Is HTTP/2 also affected? Are any other hop-by-hop headers forwarded that should not be?

The following should serve as a workaround:

acl hop-by-hop-header { req.fhdr(upgrade) -m found }
http-request deny status 400 if HTTP_3.0 hop-by-hop-header || HTTP_2.0 hop-by-hop-header

Also, this should work:

http-request deny status 400 if { req.fhdr(upgrade) -m found } !{ req.hdr(connection) -i upgrade }

This denies requests with an Upgrade: header not marked as a hop-by-hop header via the Connection: header. HAProxy already rejects HTTP/3 and (pr

Alternatively, use HTTP/2 on the backend, but note that this breaks WebSockets if your backend does not support RFC8441 and WebSockets do not fall back to HTTP/1.1. Apache HTTP Server can be made to support RFC8441 but NGINX and Varnish Cache do not support it.

@wtarreau
Copy link
Member

The upgrade header is only valid accompanied with Connection: Upgrade. Therefore it has no meaning without it and does not deserve any particular processing since Connection is forbidden on H2/H3.

@pittgi
Copy link
Author
pittgi commented Apr 14, 2025

@wtarreau you are right about that, but it still would be best practice to at least delete hop-by-hop headers, since not doing so violates RFC 9114 & RFC 7230.

Here an excerpt from RFC 9114 Section 4.2:

any message containing connection-specific fields MUST be treated as malformed.

Here an excerpt form RFC 7230 Section 6.7:

When Upgrade is sent, the sender MUST also send a Connection header
field (Section 6.1) that contains an "upgrade" connection option,
in order to prevent Upgrade from being accidentally forwarded by
intermediaries
that might not implement the listed protocols.

semantic-gap vulnerabilities only occur because of HTTP implementations not fully adhering to RFC. although upgrade is only valid accompanied with connection, letting haproxy forward to a backend that also does not fully adhere to RFC could cause trouble.

@wtarreau
Copy link
Member

I generally agree with being cleaner, I was trying to reassure about the real risks mostly. Upgrade was ressucitated with websocket and totally ignored till then, after that, implementing it was complicated enough that it's more common to see agents just not working than working, so I'm not much worried about the risk that someone would inadvertently upgrade.

With that said, on H2 we already clean it AFAIR. And it will happen to h3 as well for free as soon as we've merged the two parsers (which is still on our short-term todo list and that 3 of us have planned to work on tomorrow). It's too difficult, painful and dangerous to maintain an almost exact copy of the parsers in two distinct places and we really want to unify them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: needs-triage This issue needs to be triaged. type: bug This issue describes a bug.
Projects
None yet
Development

No branches or pull requests

3 participants
0