Kestrel
대시보드로 돌아가기
CVE-2026-88012MEDIUM· 5.3MITRENVDGHSA대응게시일: 2026. 09. 10.수정일: 2026. 09. 10.

Traefik: respondingTimeouts.readTimeout is not applied to HTTP/3, leaving slow-body uploads unbounded

DoS

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
5.3medium

이론적 심각도 점수

EPSS

예측 데이터 없음

KEV
미등재

실측 악용 기록 없음

권장 대응 기한14일 이내CISA SSVC 기준

2주 이내 패치 — 우선 조치 대상

자동화 가능외부 노출· KEV 미등재 · 자동화 가능 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

악용 경로
공격 벡터네트워크
공격 복잡도낮음
필요 권한불필요
사용자 상호작용불필요
범위불변
영향
기밀성 영향없음
무결성 영향없음
가용성 영향낮음
버전별 점수
CVSS 3.15.3MODERATE
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

상세 설명

Summary

There is a medium severity vulnerability in Traefik's HTTP/3 entry points: the respondingTimeouts settings were not applied to the HTTP/3 request path. readTimeout in particular is on by default at 60s and is documented as bounding the time to read the entire request including its body, but it is enforced as a deadline on the TCP connection, which cannot reach a QUIC stream, and Traefik's HTTP/3 server was constructed with no timeout of any kind. An unauthenticated client that trickles a request body therefore holds a request open for as long as it chooses, and with it one upstream connection per request, at negligible cost to itself. Backends with bounded connection pools are the practical pressure point.

The HTTP/3 path lost these timeouts in v2.8.2, when a quic-go API change removed the embedded http.Server that had carried them; every release from v2.8.2 onward is affected, and releases before v2.8.2 are not. Traefik v2.8.2 through v2.10.x and v3.0 through v3.6 are affected and are no longer maintained: they will not receive a patch on their own line, and the remedy for their users is to upgrade to v2.11.56 or v3.7.12.

Patches

For more information

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

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

Summary

entryPoints.<name>.transport.respondingTimeouts.readTimeout is documented as:

"Set the timeouts for incoming requests to the Traefik instance. This is the maximum
duration for reading the entire request, including the body." — Default: 60s

It is on by default and it works over HTTP/1.1 and HTTP/2. It has no effect on
HTTP/3
.

The consequence is not that a hardening option was left unset. It is that every Traefik
deployment with http3 enabled carries a 60-second bound that the operator has every
reason to believe is in force, and which is silently absent on that protocol. A single
client trickling one body byte every few seconds holds a request open indefinitely, and
with it one upstream connection per request.

readTimeout is applied as a deadline on the TCP connection. HTTP/3 does not have
one, and Traefik's HTTP/3 server is constructed with no timeout of any kind.

Steps to reproduce

No containers, VMs or cloud services — the official release binary, curl, openssl,
and a 68-line Python standard-library backend. Everything is attached.

bash
1bash reproduce.sh # readTimeout 5s, ~40 seconds
2MODE=default bash reproduce.sh # the stock 60s default, ~4 minutes

By hand:

1. Static config (conf/traefik.yml, complete and unredacted). Note there is no
respondingTimeouts block at all — this is the documented 60s default:

text
1global:
2 checkNewVersion: false
3 sendAnonymousUsage: false
4
5log:
6 level: DEBUG
7
8entryPoints:
9 websecure:
10 address: ":8443"
11 http3:
12 advertisedPort: 8443
13
14providers:
15 file:
16 filename: conf/dynamic.yml
17
18api:
19 dashboard: false

2. Dynamic config (conf/dynamic.yml):

text
1http:
2 routers:
3 backend-router:
4 rule: "PathPrefix(`/`)"
5 service: backend-svc
6 entryPoints: [websecure]
7 tls: {}
8 services:
9 backend-svc:
10 loadBalancer:
11 servers:
12 - url: "http://127.0.0.1:8080"
13tls:
14 certificates:
15 - certFile: cert.pem
16 keyFile: key.pem

3. A self-signed cert, so no CA install and no sudo:

text
1openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 30 -nodes \
2 -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

4. A backend that reads the request body before responding, as a real HTTP/1.1 server
does (backend.py, standard library only). This matters: a backend that answers the
request headers alone lets Traefik release the upstream connection immediately, which
hides the behaviour entirely.

text
1python3 backend.py 8080 &
2./traefik --configFile=conf/traefik.yml

5. The same slow upload over each protocol. The hold must exceed the timeout under
test, so 92 seconds against the 60-second default:

bash
1{ for i in $(seq 1 23); do printf 'x'; sleep 4; done; } | \
2 curl -v -k -T - --http1.1 https://localhost:8443/
3
4{ for i in $(seq 1 23); do printf 'x'; sleep 4; done; } | \
5 curl -v -k -T - --http3-only https://localhost:8443/

Result

Traefik v3.7.10 (langres, go1.26.5), official
traefik_v3.7.10_linux_amd64.tar.gz, sha256
01811bb12d44f17280550f425f5e3128d6c325f2665c09e67a651ca535f490ce.

Upstream connection lifetime measured at the backend — the client's view only shows the
absence of a server action, which proves nothing on its own:

configholdHTTP/1.1 (control)HTTP/3
stock — documented 60s default92 s59.99 s — released at the documented default92.94 s — held for the entire window
readTimeout: 5s30 s4.99 s30.98 s

The HTTP/1.1 column is the control and it is the point of the exercise: the timeout is
demonstrably live on this exact binary, releasing the upstream at its configured value.
The HTTP/3 request, in the same run against the same instance with the same setting, ran
to completion with the upstream pinned throughout. Traefik returned 499 on the aborted
HTTP/1.1 arm and took no action at all on HTTP/3 — no RST_STREAM, no
H3_REQUEST_INCOMPLETE, no connection close.

The setting is not being silently discarded: Traefik's own DEBUG log prints the loaded
static configuration including "respondingTimeouts":{"idleTimeout":"3m0s","readTimeout":"5s"}.
Full curl -v output, backend logs and Traefik DEBUG logs for every arm are in
evidence/.

Cause

readTimeout is a TCP connection deadline
pkg/server/server_entrypoint_tcp.go:273:

text
1if e.transportConfiguration.RespondingTimeouts.ReadTimeout > 0 {
2 err := writeCloser.SetReadDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.ReadTimeout)))

A deadline on a TCP connection cannot reach a QUIC stream.

And the HTTP/3 server is given no timeout of any kind
pkg/server/server_entrypoint_tcp_http3.go:65:

text
1h3.Server = &http3.Server{
2 Addr: config.GetAddress(),
3 Port: config.HTTP3.AdvertisedPort,
4 Handler: httpsServer.Server.(*http.Server).Handler,
5 TLSConfig: &tls.Config{GetConfigForClient: h3.getTLSConfigForClient},
6 QUICConfig: &quic.Config{
7 Allow0RTT: false,
8 },
9 ConnContext: func(ctx context.Context, c *quic.Conn) context.Context {

It reuses the HTTPS server's handler and inherits none of its timeouts. There is
therefore no duration control on the HTTP/3 request path at all — not readTimeout, not
idleTimeout, nothing.

Note that this cannot be fixed by passing a field through: quic-go's http3.Server
exposes no request-read deadline. The remedy has to be enforced around the request body
inside the handler, or upstream in quic-go.

Suggested remedy

In preference order:

  1. Enforce readTimeout on the HTTP/3 path in the handler. Traefik already passes the
    HTTPS server's handler to http3.Server, so when RespondingTimeouts.ReadTimeout > 0
    it can wrap r.Body for HTTP/3 requests in a reader that enforces the deadline.
  2. Add a request-read deadline upstream in quic-go's http3.Server and pass it through.
  3. At minimum, document it. See below — the current documentation does not tell an
    operator this.

A sketch of option 1

Offered as a description of the shape, not as a patch. I have not built or tested this
against Traefik, and I am not going to present untested code as though I had. If a working,
tested patch would be useful, say so and I will prepare one properly and verify it against
the reproducer above.

Where the HTTP/3 handler is wired up — server_entrypoint_tcp_http3.go, around the
http3.Server construction — the handler passed in could be wrapped so that, when
ReadTimeout is configured, an HTTP/3 request body carries the same deadline the TCP path
gets from SetReadDeadline:

text
1// Roughly: for HTTP/3 requests only, and only when the timeout is set.
2if readTimeout > 0 && r.ProtoMajor == 3 && r.Body != nil {
3 r.Body = deadlineBody(r.Body, readTimeout)
4}

The part worth knowing, because it is what makes the approach work rather than merely
look tidy: closing the request body cancels the QUIC stream read. In quic-go,
http3's body Close() calls str.CancelRead(...), which unblocks a Read that is
already parked waiting on the client. So a timer that closes the body on expiry bounds
both a client that trickles and a client that simply stops sending — the latter being
the case a wrapper that only checks the clock on each returning Read would miss
entirely.

Returning os.ErrDeadlineExceeded from the wrapped Read keeps the failure classified as
a timeout rather than a client abort, which matters for whatever status code and logging
you decide is right.

Two design questions I would not want to answer on your behalf: whether HTTP/3 should reuse
respondingTimeouts.readTimeout or get its own setting, and whether enforcement belongs in
the handler wrapper or somewhere closer to the entrypoint. Both are your call.

A documentation issue, separately

Two things in the docs are worth correcting regardless of how the code question is
resolved.

1. The readTimeout description carries no protocol qualification. It says "the
maximum duration for reading the entire request, including the body", which is exactly
what an operator relies on. The entrypoint page does note that respondingTimeouts have
"no effect for UDP entryPoints", but that does not cover this case: HTTP/3 here is served
on an HTTP entrypoint with an http3: block, not on a Traefik UDP entrypoint,
which is a separate feature for UDP routers. An operator who adds http3: to their
existing HTTPS entrypoint has not created a UDP entrypoint and has no reason to read that
caveat as applying to them.

2. SECURITY.md's supported-versions table is stale. It lists 3.6.x as supported
and < 3.6.x as unsupported, while 3.7.10 is the current release. Anyone checking whether
their version is in scope before reporting gets a confusing answer.

Impact

Each held request occupies one upstream connection for as long as the client chooses, at
negligible cost to the client, and the same client can open many. Backends with bounded
connection pools are the practical pressure point.

I have not measured a concurrency ceiling on Traefik itself, so I am not asserting
one. If that number matters to your assessment, tell me and I will measure it.

LLM ("AI") use disclosure

Not required by your policy, but stated because it is true and you should be able to
weigh it. I am a penetration tester, not a Go developer. The finding, the attack concept
and the decision to measure the upstream leg rather than the client are mine. An LLM
coding assistant (Claude) built the test harness, ran the matrix, and located the two
source citations; I verified those by hand against the v3.7.10 tag and ran the
reproducer myself. I am a human and I will be the one replying in this thread.

Disclosure

Bishop Fox operates a 90-day disclosure policy, starting the day this is submitted, with
extensions where a fix is in progress. Tell me what you would prefer and I will work to
it.

Environment

  • Traefik v3.7.10, official traefik_v3.7.10_linux_amd64.tar.gz,
    sha256 01811bb12d44f17280550f425f5e3128d6c325f2665c09e67a651ca535f490ce
  • Kali GNU/Linux (WSL2), kernel 6.6.114.1
  • curl 8.19.0 with ngtcp2 1.21.0 / nghttp3 1.15.0### Summary
    Short summary of the problem. Make the impact and severity as clear as possible. For example: An unsafe deserialization vulnerability allows any unauthenticated user to execute arbitrary code on the server.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

Complete instructions, including specific configuration details, to reproduce the vulnerability.

Impact

What kind of vulnerability is it? Who is impacted?

</details> ---

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.