CVE-2025-48936 Overview
CVE-2025-48936 is a host header injection vulnerability in Zitadel, an open-source identity infrastructure platform. The flaw exists in the password reset mechanism, where Zitadel uses the Forwarded or X-Forwarded-Host HTTP headers from incoming requests to construct password reset confirmation URLs. An attacker who manipulates these headers can cause Zitadel to generate password reset links pointing to an attacker-controlled domain. If a victim clicks the manipulated link, the embedded secret reset code is captured and can be used to take over the account. Accounts protected by Multi-Factor Authentication (MFA) or Passwordless authentication are not exposed to this specific attack path.
Critical Impact
Successful exploitation enables full account takeover for any user without MFA by capturing password reset tokens through a malicious reset URL.
Affected Products
- Zitadel versions prior to 2.70.12
- Zitadel versions 2.71.0 through 2.71.9
- Zitadel versions 3.0.0 through 3.2.1
Discovery Timeline
- 2025-05-30 - CVE-2025-48936 published to NVD
- 2025-06-04 - Last updated in NVD database
Technical Details for CVE-2025-48936
Vulnerability Analysis
The vulnerability is classified under [CWE-601] URL Redirection to Untrusted Site and [CWE-77] Command Injection. Zitadel constructs password reset confirmation URLs server-side based on the host indicated in inbound HTTP headers. The application trusted Forwarded, X-Forwarded-Host, and similar headers without validating them against an allowlist of legitimate instance domains. Reset URLs embed a secret one-time code that authorizes a password change. When the reset URL points to an attacker domain, clicking the link sends the secret code directly to the attacker. The attacker then submits the code to the legitimate Zitadel instance and resets the victim's password.
Root Cause
The root cause is unvalidated trust in client-controlled HTTP headers when constructing externally facing URLs. The WithOrigin middleware in internal/api/http/middleware/origin_interceptor.go composed the request origin from forwarded headers without enforcing that the resulting host matched a configured instance or public domain. Because the password reset email is generated from this origin, a forged X-Forwarded-Host value propagated directly into the link sent to the user.
Attack Vector
Exploitation requires network access to the Zitadel password reset endpoint and the ability to set HTTP headers on the request. The attacker triggers a password reset for a target user while injecting Host: attacker.example or X-Forwarded-Host: attacker.example. Zitadel generates a reset email containing a URL such as https://attacker.example/ui/login/password/init?code=<secret>. User interaction is required: the victim must click the link, at which point the secret code is delivered to the attacker's server.
// Patch: internal/api/http/middleware/origin_interceptor.go
// fix: validate proto header and provide https enforcement (#9975)
-func WithOrigin(fallBackToHttps bool, http1Header, http2Header string, instanceHostHeaders, publicDomainHeaders []string) mux.MiddlewareFunc {
+func WithOrigin(enforceHttps bool, http1Header, http2Header string, instanceHostHeaders, publicDomainHeaders []string) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := composeDomainContext(
r,
- fallBackToHttps,
+ enforceHttps,
// to make sure we don't break existing configurations we append the existing checked headers as well
slices.Compact(append(instanceHostHeaders, http1Header, http2Header, http_util.Forwarded, http_util.ZitadelForwarded, http_util.ForwardedFor, http_util.ForwardedHost, http_util.ForwardedProto)),
publicDomainHeaders,
Source: Zitadel commit c097887
Detection Methods for CVE-2025-48936
Indicators of Compromise
- Password reset emails containing links to domains not matching the configured Zitadel instance host.
- HTTP requests to /password/reset or related endpoints carrying X-Forwarded-Host or Forwarded headers that reference unknown domains.
- Successful password changes shortly after a reset request originating from an unusual IP address or user agent.
Detection Strategies
- Inspect reverse proxy and ingress logs for inbound Host, X-Forwarded-Host, and Forwarded header values, and alert on values outside the configured instance domain allowlist.
- Correlate password reset request events with subsequent successful password change events from different source IPs or geographies.
- Review outbound email logs for reset URLs whose hostname does not match approved Zitadel domains.
Monitoring Recommendations
- Enable verbose access logging at the load balancer in front of Zitadel and retain headers for forensic review.
- Monitor Zitadel audit events for user.password.changed actions and tie them to the originating reset request.
- Track anomalous spikes in password reset requests targeting privileged or service accounts.
How to Mitigate CVE-2025-48936
Immediate Actions Required
- Upgrade Zitadel to version 2.70.12, 2.71.10, or 3.2.2 as appropriate for your release branch.
- Enforce MFA or Passwordless authentication on all accounts, which mitigates this specific attack path even before patching.
- Audit recent password reset events and invalidate active sessions for any account where suspicious resets are observed.
Patch Information
The issue is fixed in Zitadel 2.70.12, 2.71.10, and 3.2.2. The patch refactors the WithOrigin middleware to enforce HTTPS and validate the proto header, and configures Zitadel to compose the request origin only from trusted instance host and public domain headers. See the Zitadel security advisory GHSA-93m4-mfpg-c3xf for full details.
Workarounds
- Configure the upstream reverse proxy to strip or overwrite Host, X-Forwarded-Host, and Forwarded headers with the canonical Zitadel domain before forwarding to the application.
- Restrict the set of accepted host headers at the ingress layer to an explicit allowlist of Zitadel instance domains.
- Require MFA enrollment for all users to neutralize this specific account takeover vector.
# Example NGINX configuration to pin Host headers to the canonical Zitadel domain
location / {
proxy_set_header Host zitadel.example.com;
proxy_set_header X-Forwarded-Host zitadel.example.com;
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://zitadel_upstream;
}
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

