Centrifugo: SSRF via unverified JWT claims interpolated into dynamic JWKS endpoint URL
Date: 2026-03
Affected: go/github.com/centrifugal/centrifugo/v5, go/github.com/centrifugal/centrifugo/v4, go/github.com/centrifugal/centrifugo/v3
Attack vector: ssrf
Category: vulnerability-disclosure
Severity: Critical
CVE: CVE-2026-32301
CWE: CWE-918
CVSS: 9.3 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N)

### Summary Centrifugo is vulnerable to Server-Side Request Forgery (SSRF) when configured with a dynamic JWKS endpoint URL using template variables (e.g. `{{tenant}}`). An unauthenticated attacker can craft a JWT with a malicious `iss` or `aud` claim value that gets interpolated into the JWKS fetch URL **before the token signature is verified**, causing Centrifugo to make an outbound HTTP request to an attacker-controlled destination. ### Details In `internal/jwtverify/token_verifier_jwt.go`, the functions `VerifyConnectToken` and `VerifySubscribeToken` follow this flawed order of operations: 1. Token is parsed without verification: `jwt.ParseNoVerify([]byte(t))` 2. Claims are decoded from the unverified token 3. `validateClaims()` runs — extracting named regex capture groups from `issuer_regex`/`audience_regex` into `tokenVars` map using attacker-controlled `iss`/`aud` claim values 4. `verifySignatureByJWK(token, tokenVars)` is called — passing attacker-controlled `tokenVars` to the JWKS manager 5. In `internal/jwks/manager.go`, `fetchKey()` interpolates `tokenVars` directly into the JWKS URL: `jwkURL := m.url.ExecuteString(tokenVars)` 6. Centrifugo makes an HTTP GET request to the attacker-controlled URL Suppressed the security linter on this line with an incorrect comment: `//nolint:gosec // URL is from server configuration, not user input.` The URL is NOT purely from server configuration — it is partially constructed from unverified user-supplied JWT


Mitigations:

Architectural layers (MAESTRO):