VYPR
High severityNVD Advisory· Published May 30, 2025· Updated May 30, 2025

ZITADEL Allows Account Takeover via Malicious X-Forwarded-Proto Header Injection

CVE-2025-48936

Description

Zitadel is open-source identity infrastructure software. Prior to versions 2.70.12, 2.71.10, and 3.2.2, a potential vulnerability exists in the password reset mechanism. ZITADEL utilizes the Forwarded or X-Forwarded-Host header from incoming requests to construct the URL for the password reset confirmation link. This link, containing a secret code, is then emailed to the user. If an attacker can manipulate these headers (e.g., via host header injection), they could cause ZITADEL to generate a password reset link pointing to a malicious domain controlled by the attacker. If the user clicks this manipulated link in the email, the secret reset code embedded in the URL can be captured by the attacker. This captured code could then be used to reset the user's password and gain unauthorized access to their account. This specific attack vector is mitigated for accounts that have Multi-Factor Authentication (MFA) or Passwordless authentication enabled. This issue has been patched in versions 2.70.12, 2.71.10, and 3.2.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/zitadel/zitadelGo
< 0.0.0-20250528081227-c097887bc5f60.0.0-20250528081227-c097887bc5f6
github.com/zitadel/zitadel/v2Go
>= 2.38.3, < 2.70.122.70.12
github.com/zitadel/zitadel/v2Go
>= 3.0.0-rc1, < 3.2.23.2.2
github.com/zitadel/zitadel/v2Go
>= 2.71.0, < 2.71.112.71.11
github.com/zitadel/zitadel/v2Go
>= 0

Affected products

1

Patches

1
c097887bc5f6

fix: validate proto header and provide https enforcement (#9975)

https://github.com/zitadel/zitadelLivio SpringMay 28, 2025via ghsa
2 files changed · +39 35
  • internal/api/http/middleware/origin_interceptor.go+18 14 modified
    @@ -10,12 +10,12 @@ import (
     	http_util "github.com/zitadel/zitadel/internal/api/http"
     )
     
    -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,
    @@ -25,28 +25,32 @@ func WithOrigin(fallBackToHttps bool, http1Header, http2Header string, instanceH
     	}
     }
     
    -func composeDomainContext(r *http.Request, fallBackToHttps bool, instanceDomainHeaders, publicDomainHeaders []string) *http_util.DomainCtx {
    +func composeDomainContext(r *http.Request, enforceHttps bool, instanceDomainHeaders, publicDomainHeaders []string) *http_util.DomainCtx {
     	instanceHost, instanceProto := hostFromRequest(r, instanceDomainHeaders)
     	publicHost, publicProto := hostFromRequest(r, publicDomainHeaders)
    -	if publicProto == "" {
    -		publicProto = instanceProto
    -	}
    -	if publicProto == "" {
    -		publicProto = "http"
    -		if fallBackToHttps {
    -			publicProto = "https"
    -		}
    -	}
     	if instanceHost == "" {
     		instanceHost = r.Host
     	}
     	return &http_util.DomainCtx{
     		InstanceHost: instanceHost,
    -		Protocol:     publicProto,
    +		Protocol:     protocolFromRequest(instanceProto, publicProto, enforceHttps),
     		PublicHost:   publicHost,
     	}
     }
     
    +func protocolFromRequest(instanceProto, publicProto string, enforceHttps bool) string {
    +	if enforceHttps {
    +		return "https"
    +	}
    +	if publicProto != "" {
    +		return publicProto
    +	}
    +	if instanceProto != "" {
    +		return instanceProto
    +	}
    +	return "http"
    +}
    +
     func hostFromRequest(r *http.Request, headers []string) (host, proto string) {
     	var hostFromHeader, protoFromHeader string
     	for _, header := range headers {
    @@ -65,7 +69,7 @@ func hostFromRequest(r *http.Request, headers []string) (host, proto string) {
     		if host == "" {
     			host = hostFromHeader
     		}
    -		if proto == "" {
    +		if proto == "" && (protoFromHeader == "http" || protoFromHeader == "https") {
     			proto = protoFromHeader
     		}
     	}
    
  • internal/api/http/middleware/origin_interceptor_test.go+21 21 modified
    @@ -11,8 +11,8 @@ import (
     
     func Test_composeOrigin(t *testing.T) {
     	type args struct {
    -		h               http.Header
    -		fallBackToHttps bool
    +		h            http.Header
    +		enforceHttps bool
     	}
     	tests := []struct {
     		name string
    @@ -30,7 +30,7 @@ func Test_composeOrigin(t *testing.T) {
     			h: http.Header{
     				"Forwarded": []string{"proto=https"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "host.header",
    @@ -42,7 +42,7 @@ func Test_composeOrigin(t *testing.T) {
     			h: http.Header{
     				"Forwarded": []string{"host=forwarded.host"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "forwarded.host",
    @@ -54,7 +54,7 @@ func Test_composeOrigin(t *testing.T) {
     			h: http.Header{
     				"Forwarded": []string{"proto=https;host=forwarded.host"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "forwarded.host",
    @@ -66,7 +66,7 @@ func Test_composeOrigin(t *testing.T) {
     			h: http.Header{
     				"Forwarded": []string{"proto=https;host=forwarded.host, proto=http;host=forwarded.host2"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "forwarded.host",
    @@ -78,7 +78,7 @@ func Test_composeOrigin(t *testing.T) {
     			h: http.Header{
     				"Forwarded": []string{"proto=https;host=forwarded.host, proto=http"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "forwarded.host",
    @@ -90,19 +90,19 @@ func Test_composeOrigin(t *testing.T) {
     			h: http.Header{
     				"Forwarded": []string{"proto=http", "proto=https;host=forwarded.host", "proto=http"},
     			},
    -			fallBackToHttps: true,
    +			enforceHttps: true,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "forwarded.host",
    -			Protocol:     "http",
    +			Protocol:     "https",
     		},
     	}, {
     		name: "x-forwarded-proto https",
     		args: args{
     			h: http.Header{
     				"X-Forwarded-Proto": []string{"https"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "host.header",
    @@ -114,25 +114,25 @@ func Test_composeOrigin(t *testing.T) {
     			h: http.Header{
     				"X-Forwarded-Proto": []string{"http"},
     			},
    -			fallBackToHttps: true,
    +			enforceHttps: true,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "host.header",
    -			Protocol:     "http",
    +			Protocol:     "https",
     		},
     	}, {
     		name: "fallback to http",
     		args: args{
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "host.header",
     			Protocol:     "http",
     		},
     	}, {
    -		name: "fallback to https",
    +		name: "enforce https",
     		args: args{
    -			fallBackToHttps: true,
    +			enforceHttps: true,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "host.header",
    @@ -144,7 +144,7 @@ func Test_composeOrigin(t *testing.T) {
     			h: http.Header{
     				"X-Forwarded-Host": []string{"x-forwarded.host"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "x-forwarded.host",
    @@ -157,7 +157,7 @@ func Test_composeOrigin(t *testing.T) {
     				"X-Forwarded-Proto": []string{"https"},
     				"X-Forwarded-Host":  []string{"x-forwarded.host"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "x-forwarded.host",
    @@ -170,7 +170,7 @@ func Test_composeOrigin(t *testing.T) {
     				"Forwarded":        []string{"host=forwarded.host"},
     				"X-Forwarded-Host": []string{"x-forwarded.host"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "forwarded.host",
    @@ -183,7 +183,7 @@ func Test_composeOrigin(t *testing.T) {
     				"Forwarded":         []string{"host=forwarded.host"},
     				"X-Forwarded-Proto": []string{"https"},
     			},
    -			fallBackToHttps: false,
    +			enforceHttps: false,
     		},
     		want: &http_util.DomainCtx{
     			InstanceHost: "forwarded.host",
    @@ -198,10 +198,10 @@ func Test_composeOrigin(t *testing.T) {
     					Host:   "host.header",
     					Header: tt.args.h,
     				},
    -				tt.args.fallBackToHttps,
    +				tt.args.enforceHttps,
     				[]string{http_util.Forwarded, http_util.ForwardedFor, http_util.ForwardedHost, http_util.ForwardedProto},
     				[]string{"x-zitadel-public-host"},
    -			), "headers: %+v, fallBackToHttps: %t", tt.args.h, tt.args.fallBackToHttps)
    +			), "headers: %+v, enforceHttps: %t", tt.args.h, tt.args.enforceHttps)
     		})
     	}
     }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.