Skip to content

Commit c097887

Browse files
authored
fix: validate proto header and provide https enforcement (zitadel#9975)
# Which Problems Are Solved ZITADEL uses the notification triggering requests Forwarded or X-Forwarded-Proto header to build the button link sent in emails for confirming a password reset with the emailed code. If this header is overwritten and a user clicks the link to a malicious site in the email, the secret code can be retrieved and used to reset the users password and take over his account. Accounts with MFA or Passwordless enabled can not be taken over by this attack. # How the Problems Are Solved - The `X-Forwarded-Proto` and `proto` of the Forwarded headers are validated (http / https). - Additionally, when exposing ZITADEL through https. An overwrite to http is no longer possible. # Additional Changes None # Additional Context None
1 parent 77b4333 commit c097887

File tree

2 files changed

+39
-35
lines changed

2 files changed

+39
-35
lines changed

internal/api/http/middleware/origin_interceptor.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import (
1010
http_util "github.com/zitadel/zitadel/internal/api/http"
1111
)
1212

13-
func WithOrigin(fallBackToHttps bool, http1Header, http2Header string, instanceHostHeaders, publicDomainHeaders []string) mux.MiddlewareFunc {
13+
func WithOrigin(enforceHttps bool, http1Header, http2Header string, instanceHostHeaders, publicDomainHeaders []string) mux.MiddlewareFunc {
1414
return func(next http.Handler) http.Handler {
1515
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1616
origin := composeDomainContext(
1717
r,
18-
fallBackToHttps,
18+
enforceHttps,
1919
// to make sure we don't break existing configurations we append the existing checked headers as well
2020
slices.Compact(append(instanceHostHeaders, http1Header, http2Header, http_util.Forwarded, http_util.ZitadelForwarded, http_util.ForwardedFor, http_util.ForwardedHost, http_util.ForwardedProto)),
2121
publicDomainHeaders,
@@ -25,28 +25,32 @@ func WithOrigin(fallBackToHttps bool, http1Header, http2Header string, instanceH
2525
}
2626
}
2727

28-
func composeDomainContext(r *http.Request, fallBackToHttps bool, instanceDomainHeaders, publicDomainHeaders []string) *http_util.DomainCtx {
28+
func composeDomainContext(r *http.Request, enforceHttps bool, instanceDomainHeaders, publicDomainHeaders []string) *http_util.DomainCtx {
2929
instanceHost, instanceProto := hostFromRequest(r, instanceDomainHeaders)
3030
publicHost, publicProto := hostFromRequest(r, publicDomainHeaders)
31-
if publicProto == "" {
32-
publicProto = instanceProto
33-
}
34-
if publicProto == "" {
35-
publicProto = "http"
36-
if fallBackToHttps {
37-
publicProto = "https"
38-
}
39-
}
4031
if instanceHost == "" {
4132
instanceHost = r.Host
4233
}
4334
return &http_util.DomainCtx{
4435
InstanceHost: instanceHost,
45-
Protocol: publicProto,
36+
Protocol: protocolFromRequest(instanceProto, publicProto, enforceHttps),
4637
PublicHost: publicHost,
4738
}
4839
}
4940

41+
func protocolFromRequest(instanceProto, publicProto string, enforceHttps bool) string {
42+
if enforceHttps {
43+
return "https"
44+
}
45+
if publicProto != "" {
46+
return publicProto
47+
}
48+
if instanceProto != "" {
49+
return instanceProto
50+
}
51+
return "http"
52+
}
53+
5054
func hostFromRequest(r *http.Request, headers []string) (host, proto string) {
5155
var hostFromHeader, protoFromHeader string
5256
for _, header := range headers {
@@ -65,7 +69,7 @@ func hostFromRequest(r *http.Request, headers []string) (host, proto string) {
6569
if host == "" {
6670
host = hostFromHeader
6771
}
68-
if proto == "" {
72+
if proto == "" && (protoFromHeader == "http" || protoFromHeader == "https") {
6973
proto = protoFromHeader
7074
}
7175
}

internal/api/http/middleware/origin_interceptor_test.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111

1212
func Test_composeOrigin(t *testing.T) {
1313
type args struct {
14-
h http.Header
15-
fallBackToHttps bool
14+
h http.Header
15+
enforceHttps bool
1616
}
1717
tests := []struct {
1818
name string
@@ -30,7 +30,7 @@ func Test_composeOrigin(t *testing.T) {
3030
h: http.Header{
3131
"Forwarded": []string{"proto=https"},
3232
},
33-
fallBackToHttps: false,
33+
enforceHttps: false,
3434
},
3535
want: &http_util.DomainCtx{
3636
InstanceHost: "host.header",
@@ -42,7 +42,7 @@ func Test_composeOrigin(t *testing.T) {
4242
h: http.Header{
4343
"Forwarded": []string{"host=forwarded.host"},
4444
},
45-
fallBackToHttps: false,
45+
enforceHttps: false,
4646
},
4747
want: &http_util.DomainCtx{
4848
InstanceHost: "forwarded.host",
@@ -54,7 +54,7 @@ func Test_composeOrigin(t *testing.T) {
5454
h: http.Header{
5555
"Forwarded": []string{"proto=https;host=forwarded.host"},
5656
},
57-
fallBackToHttps: false,
57+
enforceHttps: false,
5858
},
5959
want: &http_util.DomainCtx{
6060
InstanceHost: "forwarded.host",
@@ -66,7 +66,7 @@ func Test_composeOrigin(t *testing.T) {
6666
h: http.Header{
6767
"Forwarded": []string{"proto=https;host=forwarded.host, proto=http;host=forwarded.host2"},
6868
},
69-
fallBackToHttps: false,
69+
enforceHttps: false,
7070
},
7171
want: &http_util.DomainCtx{
7272
InstanceHost: "forwarded.host",
@@ -78,7 +78,7 @@ func Test_composeOrigin(t *testing.T) {
7878
h: http.Header{
7979
"Forwarded": []string{"proto=https;host=forwarded.host, proto=http"},
8080
},
81-
fallBackToHttps: false,
81+
enforceHttps: false,
8282
},
8383
want: &http_util.DomainCtx{
8484
InstanceHost: "forwarded.host",
@@ -90,19 +90,19 @@ func Test_composeOrigin(t *testing.T) {
9090
h: http.Header{
9191
"Forwarded": []string{"proto=http", "proto=https;host=forwarded.host", "proto=http"},
9292
},
93-
fallBackToHttps: true,
93+
enforceHttps: true,
9494
},
9595
want: &http_util.DomainCtx{
9696
InstanceHost: "forwarded.host",
97-
Protocol: "http",
97+
Protocol: "https",
9898
},
9999
}, {
100100
name: "x-forwarded-proto https",
101101
args: args{
102102
h: http.Header{
103103
"X-Forwarded-Proto": []string{"https"},
104104
},
105-
fallBackToHttps: false,
105+
enforceHttps: false,
106106
},
107107
want: &http_util.DomainCtx{
108108
InstanceHost: "host.header",
@@ -114,25 +114,25 @@ func Test_composeOrigin(t *testing.T) {
114114
h: http.Header{
115115
"X-Forwarded-Proto": []string{"http"},
116116
},
117-
fallBackToHttps: true,
117+
enforceHttps: true,
118118
},
119119
want: &http_util.DomainCtx{
120120
InstanceHost: "host.header",
121-
Protocol: "http",
121+
Protocol: "https",
122122
},
123123
}, {
124124
name: "fallback to http",
125125
args: args{
126-
fallBackToHttps: false,
126+
enforceHttps: false,
127127
},
128128
want: &http_util.DomainCtx{
129129
InstanceHost: "host.header",
130130
Protocol: "http",
131131
},
132132
}, {
133-
name: "fallback to https",
133+
name: "enforce https",
134134
args: args{
135-
fallBackToHttps: true,
135+
enforceHttps: true,
136136
},
137137
want: &http_util.DomainCtx{
138138
InstanceHost: "host.header",
@@ -144,7 +144,7 @@ func Test_composeOrigin(t *testing.T) {
144144
h: http.Header{
145145
"X-Forwarded-Host": []string{"x-forwarded.host"},
146146
},
147-
fallBackToHttps: false,
147+
enforceHttps: false,
148148
},
149149
want: &http_util.DomainCtx{
150150
InstanceHost: "x-forwarded.host",
@@ -157,7 +157,7 @@ func Test_composeOrigin(t *testing.T) {
157157
"X-Forwarded-Proto": []string{"https"},
158158
"X-Forwarded-Host": []string{"x-forwarded.host"},
159159
},
160-
fallBackToHttps: false,
160+
enforceHttps: false,
161161
},
162162
want: &http_util.DomainCtx{
163163
InstanceHost: "x-forwarded.host",
@@ -170,7 +170,7 @@ func Test_composeOrigin(t *testing.T) {
170170
"Forwarded": []string{"host=forwarded.host"},
171171
"X-Forwarded-Host": []string{"x-forwarded.host"},
172172
},
173-
fallBackToHttps: false,
173+
enforceHttps: false,
174174
},
175175
want: &http_util.DomainCtx{
176176
InstanceHost: "forwarded.host",
@@ -183,7 +183,7 @@ func Test_composeOrigin(t *testing.T) {
183183
"Forwarded": []string{"host=forwarded.host"},
184184
"X-Forwarded-Proto": []string{"https"},
185185
},
186-
fallBackToHttps: false,
186+
enforceHttps: false,
187187
},
188188
want: &http_util.DomainCtx{
189189
InstanceHost: "forwarded.host",
@@ -198,10 +198,10 @@ func Test_composeOrigin(t *testing.T) {
198198
Host: "host.header",
199199
Header: tt.args.h,
200200
},
201-
tt.args.fallBackToHttps,
201+
tt.args.enforceHttps,
202202
[]string{http_util.Forwarded, http_util.ForwardedFor, http_util.ForwardedHost, http_util.ForwardedProto},
203203
[]string{"x-zitadel-public-host"},
204-
), "headers: %+v, fallBackToHttps: %t", tt.args.h, tt.args.fallBackToHttps)
204+
), "headers: %+v, enforceHttps: %t", tt.args.h, tt.args.enforceHttps)
205205
})
206206
}
207207
}

0 commit comments

Comments
 (0)