# Traefik has a Potential mTLS Bypass via Fragmented TLS ClientHello Causing Pre-SNI Sniff Fallback to Default Non-mTLS TLS Config

Fragmented TLS ClientHello makes Traefik's SNI pre-sniff return empty SNI, falling back to default NoClientCert TLS config and bypassing route-level mTLS.

- Payout: Research
- Severity: high
- Program: Traefik
- Found by: InfinityHub123
- Disclosed: 2026-03-20
- CVE: CVE-2026-32305
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-wvvq-wgcr-9q48
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-wvvq-wgcr-9q48
- Tags: area:devices domain:infra domain:network impact:internal-access impact:privesc kind:research platform:ghsa sev:high tech:go technique:parser-differential vuln:auth-bypass vuln:misconfiguration

**Target:** Traefik TCP router TLS SNI pre-sniffing / TLS options
**Root cause:** clientHelloInfo peeks only one TLS record (5+recLen bytes) and parses it for SNI; a fragmented ClientHello yields EOF/empty SNI, so the router applies the default TLS config (NoClientCert) instead of the route's RequireAndVerifyClientCert options, while the real Go TLS handshake later parses the full ClientHello.
**Steps:**
1. Deploy Traefik with a router for `whoami.home.arpa` using tls options `mtls` (clientAuthType: RequireAndVerifyClientCert) and default TLS config left permissive.
2. Baseline: non-fragmented ClientHello without client cert fails (cert requested, none provided).
3. Send a fragmented ClientHello using tlsfuzzer with `SetMaxRecordSize(500)` and a padding extension (`TLSExtension().create(21, bytearray(1200))`) so the first record is incomplete for full ClientHello parsing.
4. Traefik pre-sniff returns EOF/empty SNI, applies default NoClientCert config; handshake completes without client cert and request reaches the protected backend.
**How found:** Reporter noticed a mismatch between where Traefik decides TLS policy per host (router.go ServeTCP -> clientHelloInfo peeking only one record) and where Go TLS finally parses the full ClientHello; hypothesized that fragmentation would break SNI extraction and cause fallback to the default TLS config.
**Exploitation:** Attacker crafts a TLS ClientHello split across multiple records (small max record size + padding extension) so Traefik's pre-sniff SNI extraction fails with EOF. Traefik then selects the default permissive TLS config (NoClientCert) instead of the route's mTLS options. The subsequent Go TLS handshake parses the full ClientHello and completes without a client certificate, and the HTTP request is routed to the mTLS-protected host.
**Insight:** When a proxy sniffs only the first TLS record to make a security decision (SNI-based policy), test whether splitting the ClientHello across records changes the decision while the real TLS stack still parses it — a parser-differential between the sniffer and the actual handshake.

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

**Traefik mTLS config and vulnerable router setup**

```
http:
  routers:
    whoami:
      rule: "Host(`whoami.home.arpa`)"
      entryPoints:
        - websecure
      service: whoami
      tls:
        options: mtls

  services:
    whoami:
      loadBalancer:
        servers:
          - url: "http://whoami:80"

tls:
  certificates:
    - certFile: /certs/traefik.crt
      keyFile: /certs/traefik.key

  options:
    mtls:
      clientAuth:
        caFiles:
          - /certs/ca.crt
        clientAuthType: RequireAndVerifyClientCert
```

**Bypass invocation with fragmented record size**

```
python frag_clienthello.py \
  --connect-host 127.0.0.1 \
  --port 8443 \
  --sni whoami.home.arpa \
  --record-size 500
# in the record-size you can play with it as long as the client hello sni sniff function returns an EOF
```

