Listen to this Post
How CVE-2026-49998 Works
Centrifugo’s dynamic JWKS endpoint feature allows administrators to configure templated URLs that resolve to different JSON Web Key Sets based on JWT claims such as `iss` (issuer) or `aud` (audience). This is commonly used in multi-tenant deployments where each tenant has its own JWKS endpoint, e.g., https://auth.example.com/{{tenant}}/.well-known/jwks.json`.iss
The vulnerability stems from a fundamental flaw in how Centrifugo caches public keys fetched from these endpoints. When a JWT is presented for verification, Centrifugo parses the token without verifying its signature first. It extracts template variables from the unverified claims (e.g., the `tenant` value from), computes the JWKS URL, and fetches the public key. However, the cache lookup—both in the TTL cache and the `singleflight` deduplication mechanism—is keyed only by the JWT header `kid` (Key ID), not by the resolved endpoint URL, issuer, audience, or any other trust-domain namespace.“current”
This creates a cross-tenant authentication bypass. An attacker who can obtain or mint a valid token for tenant A can authenticate as tenant B if both JWKS documents use the same `kid` value and tenant A's key is cached first. Because `kid` values are not globally unique by specification and are often operational labels like,“default”, or rotation identifiers, this collision scenario is practical.VerifyConnectToken
The vulnerable code paths affect both connection token verification () and subscription token verification (VerifySubscribeToken), as both use the same JWKS verification manager. The cache stores and retrieves keys solely by `kid` ininternal/jwks/cache_ttl.go:82-101, and the `singleflight.Group.Do` call in `internal/jwks/manager.go:96-117` uses only the `kid` as the deduplication key. The resolved JWKS URL is computed after the cache check, ininternal/jwks/manager.go:133-149. Consequently, a key fetched from tenant A's JWKS endpoint can be reused to verify a token claiming tenant B before tenant B's JWKS endpoint is ever consulted.
The project's template safety mitigation—which restricts placeholder regex groups to finite literal alternatives—prevents arbitrary endpoint substitution but does not scope cached keys by the resolved endpoint or issuer/audience namespace. The official proof-of-concept uses a validator-accepted issuer regex `^(?P
<h2 style="color: blue;">DailyCVE Form</h2>
Platform: Centrifugo
Version: <= 6.6.2
Vulnerability: JWKS cache kid collision
Severity: High
Date: 2026-07-01
<h2 style="color: blue;">Prediction: 2026-03-12 (fixed v6.7.0)</h2>
<h2 style="color: blue;">What Undercode Say: Analytics</h2>
The following analytics and commands are derived from the official vulnerability report and proof-of-concept.
<h2 style="color: blue;">Vulnerable Configuration Check:</h2>
Check if dynamic JWKS endpoint is configured with template variables
grep -E "jwks_public_endpoint.\{\{" config.json
<h2 style="color: blue;">Affected Configuration Options:</h2>
- `client.token.jwks_public_endpoint`
- `client.subscription_token.jwks_public_endpoint`
<h2 style="color: blue;">Source Code Locations (Centrifugo <= 6.6.2):</h2>
- Config fields: `internal/configtypes/types.go:59-65`
- Verifier config: `internal/confighelpers/jwt.go:36-41`
- Config schema: `internal/cli/configdoc/schema.json:3927, 3947, 3967, 3987, 4069, 4089, 4109, 4129`
- Dynamic JWKS docs: `CHANGELOG.md:107`
-VerifyConnectToken: `internal/client/handler.go:350-352`VerifySubscribeToken
-: `internal/client/handler.go:769-775` and `628-632`
- Token parse (no verify): `internal/jwtverify/token_verifier_jwt.go:528-535`
- Template extraction: `internal/jwtverify/token_verifier_jwt.go:539-548`
- Claim validation (after sig): `internal/jwtverify/token_verifier_jwt.go:557-560`
- JWKS cache lookup (kid only): `internal/jwtverify/token_verifier_jwt.go:242-245`
- Cache/singleflight (kid only): `internal/jwks/manager.go:96-117`
- URL resolution (after cache): `internal/jwks/manager.go:133-149`
- TTL cache store/get: `internal/jwks/cache_ttl.go:82-101`
<h2 style="color: blue;">Run the Official PoC:</h2>
From a clean checkout at the vulnerable commit git checkout 458ee0500f046877d7e8375e32f5e842bc95535b go test ./internal/jwtverify -run TestJWKSCacheKeyIsNotScopedToTemplatedEndpointPoC -count=1 -v
<h2 style="color: blue;">PoC Test Assertions (frominternal/jwtverify/jwks_cache_poc_test.go):</h2>victim
- Legitimate tenant-B token signed by tenant B → succeeds with fresh verifier.
- Forged tenant-B token signed by tenant A → fails with fresh verifier.
- Legitimate tenant-A token succeeds and primes cache with tenant A's `shared-kid` key.
- Forged tenant-B token signed by tenant A then succeeds with user ID.aud
- Tenant-B JWKS request counter does not increase during forged verification, proving cache hit.
<h2 style="color: blue;">Exploit: Cross-Tenant Authentication Bypass</h2>
<h2 style="color: blue;">Prerequisites:</h2>
1. Centrifugo configured with dynamic JWKS endpoint using template variables (e.g., `{{tenant}}` from `iss` or).“default”
2. Multi-tenant deployment where different tenants use the same `kid` value in their JWKS documents.
3. Attacker can obtain or mint a valid token for one allowed tenant (tenant A).
<h2 style="color: blue;">Exploitation Steps:</h2>
1. Identify the `kid` collision. The attacker discovers that both tenant A and tenant B use the same `kid` (e.g., `"current"` or) in their JWKS.kid
2. Obtain a valid token for tenant A. The attacker either has a legitimate token or can mint one using tenant A's private key.
3. Prime the cache. The attacker sends the legitimate tenant-A token to Centrifugo. The server fetches tenant A's JWKS, caches the public key under, and accepts the token.iss: tenant-b
4. Forge a token for tenant B. The attacker signs a JWT claiming to be from tenant B () but uses tenant A's private key. The JWT header contains the same `kid` value.kid
5. Send the forged token. Centrifugo looks up the cache by, finds tenant A's public key, verifies the signature successfully, and accepts the forged token as a legitimate tenant-B token.aud
Result: The attacker authenticates as a user in tenant B's namespace without ever possessing tenant B's private key or fetching tenant B's JWKS.
Note: A separate but related SSRF vulnerability (CVE-2026-32301) exists in the same dynamic JWKS feature, where unverified JWT claims can cause Centrifugo to make outbound HTTP requests to attacker-controlled destinations.
<h2 style="color: blue;">Protection: Mitigating CVE-2026-49998</h2>
<h2 style="color: blue;">Immediate Remediation:</h2>
Upgrade to Centrifugo v6.7.0 or higher. Version 6.7.0 contains the fix that scopes JWKS cache entries by the resolved trust domain.
<h2 style="color: blue;">If Upgrade Is Not Immediately Possible:</h2>
- Workaround: Restrict template variables in the JWKS endpoint URL to only the `kid` header field, rather than allowing arbitrary claim values like `iss` or.{{tenant}}`-style templates.
- Avoid shared `kid` values across tenants. Ensure each tenant's JWKS uses a unique `kid` that is not reused across different trust domains.
- Disable dynamic JWKS if not strictly required, and use static JWKS endpoints.
<h2 style="color: blue;">Verification of Fix:</h2>
After upgrading, the PoC test should fail as expected—the forged tenant-B token signed by tenant A should remain rejected after tenant A primes the cache. The cache now uses a composite key such as `resolved_jwks_url + "\x00" + kid` or an equivalent canonical trust-domain identifier.
<h2 style="color: blue;">Impact</h2>
Severity: High
Confidentiality Impact: Significant—attackers can impersonate users in other tenant namespaces.
Integrity Impact: Limited—attackers can forge authentication but not modify existing data.
<h2 style="color: blue;">Availability Impact: None.</h2>
<h2 style="color: blue;">Attack Vector: Network</h2>
<h2 style="color: blue;">Attack Complexity: Low—does not require special conditions</h2>
<h2 style="color: blue;">Privileges Required: None</h2>
<h2 style="color: blue;">User Interaction: None</h2>
<h2 style="color: blue;">Exploit Maturity: Proof-of-concept available</h2>
<h2 style="color: blue;">EPSS Score: 0.11% (29th percentile)</h2>
<h2 style="color: blue;">Affected Deployments:</h2>
- Multi-tenant Centrifugo deployments using dynamic JWKS endpoints with
– Deployments where different tenants’ JWKS documents reuse the same `kid` value.
Potential Consequences:
- Authentication as a user in another issuer/tenant namespace.
- Unauthorized connection-token acceptance.
- Unauthorized subscription-token acceptance where separate subscription JWTs are configured.
- Cross-tenant confidentiality and integrity impact when issuer-derived JWKS endpoints are used as separate trust domains.
References:
- CVE-2026-49998
- GHSA-g6vg-wj8f-48cj
- Fix commit: Scope JWKS cache by resolved endpoint URL (1142)
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

