mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-11 00:25:12 +00:00
Merge commit from fork
* fix: sanitize host headers before use * add additional test
This commit is contained in:
@@ -88,10 +88,10 @@ func setInstance(ctx context.Context, verifier authz.InstanceVerifier) (_ contex
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
requestContext := zitadel_http.DomainContext(ctx)
|
||||
if requestContext.InstanceHost == "" {
|
||||
if requestContext.InstanceDomain() == "" {
|
||||
return nil, zerrors.ThrowNotFound(err, "INST-zWq7X", "Errors.IAM.NotFound")
|
||||
}
|
||||
instance, err := verifier.InstanceByHost(authCtx, requestContext.InstanceHost, requestContext.PublicHost)
|
||||
instance, err := verifier.InstanceByHost(authCtx, requestContext.InstanceDomain(), requestContext.RequestedDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/muhlemmer/httpforwarded"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
)
|
||||
@@ -16,7 +20,7 @@ func WithOrigin(enforceHttps bool, http1Header, http2Header string, instanceHost
|
||||
origin := composeDomainContext(
|
||||
r,
|
||||
enforceHttps,
|
||||
// to make sure we don't break existing configurations we append the existing checked headers as well
|
||||
// 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,17 +29,13 @@ func WithOrigin(enforceHttps bool, http1Header, http2Header string, instanceHost
|
||||
}
|
||||
}
|
||||
|
||||
func composeDomainContext(r *http.Request, enforceHttps 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 instanceHost == "" {
|
||||
instanceHost = r.Host
|
||||
}
|
||||
return &http_util.DomainCtx{
|
||||
InstanceHost: instanceHost,
|
||||
Protocol: protocolFromRequest(instanceProto, publicProto, enforceHttps),
|
||||
PublicHost: publicHost,
|
||||
}
|
||||
return http_util.NewDomainCtx(instanceHost, publicHost, protocolFromRequest(instanceProto, publicProto, enforceHttps))
|
||||
}
|
||||
|
||||
func protocolFromRequest(instanceProto, publicProto string, enforceHttps bool) string {
|
||||
@@ -67,7 +67,7 @@ func hostFromRequest(r *http.Request, headers []string) (host, proto string) {
|
||||
hostFromHeader = r.Header.Get(header)
|
||||
}
|
||||
if host == "" {
|
||||
host = hostFromHeader
|
||||
host = sanitizeHost(hostFromHeader)
|
||||
}
|
||||
if proto == "" && (protoFromHeader == "http" || protoFromHeader == "https") {
|
||||
proto = protoFromHeader
|
||||
@@ -76,6 +76,35 @@ func hostFromRequest(r *http.Request, headers []string) (host, proto string) {
|
||||
return host, proto
|
||||
}
|
||||
|
||||
func sanitizeHost(rawHost string) (host string) {
|
||||
if rawHost == "" {
|
||||
return ""
|
||||
}
|
||||
host, port, err := net.SplitHostPort(rawHost)
|
||||
if err != nil {
|
||||
// if the error is about a missing port, the host is probably just "example.com", so we can return it
|
||||
if isMissingPortError(err) {
|
||||
return rawHost
|
||||
}
|
||||
// if the error is about something else, the host is probably invalid, so we log it and return an empty string
|
||||
logging.WithFields("host", rawHost).Warning("invalid host header, ignoring header")
|
||||
return ""
|
||||
}
|
||||
// if the port is not numeric, the host was probably something like "localhost:@attacker.com"
|
||||
portNumber, err := strconv.Atoi(port)
|
||||
if err != nil || portNumber < 1 || portNumber > 65535 {
|
||||
logging.WithFields("host", rawHost).Warning("invalid port in host header, ignoring header")
|
||||
return ""
|
||||
}
|
||||
// if we reach this point, the host contains a valid port, so we return the complete host
|
||||
return rawHost
|
||||
}
|
||||
|
||||
func isMissingPortError(err error) bool {
|
||||
var addrErr *net.AddrError
|
||||
return errors.As(err, &addrErr) && (addrErr.Err == "missing port in address")
|
||||
}
|
||||
|
||||
func hostFromForwarded(values []string) (string, string) {
|
||||
fwd, fwdErr := httpforwarded.Parse(values)
|
||||
if fwdErr == nil {
|
||||
|
||||
@@ -205,3 +205,75 @@ func Test_composeOrigin(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_sanitizeHost(t *testing.T) {
|
||||
type args struct {
|
||||
rawHost string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "normal host",
|
||||
args: args{rawHost: "example.com"},
|
||||
want: "example.com",
|
||||
},
|
||||
{
|
||||
name: "host with port",
|
||||
args: args{rawHost: "example.com:8080"},
|
||||
want: "example.com:8080",
|
||||
},
|
||||
{
|
||||
name: "ipv4",
|
||||
args: args{rawHost: "192.168.1.1"},
|
||||
want: "192.168.1.1",
|
||||
},
|
||||
{
|
||||
name: "ipv4 with port",
|
||||
args: args{rawHost: "192.168.1.1:8080"},
|
||||
want: "192.168.1.1:8080",
|
||||
},
|
||||
{
|
||||
name: "ipv6",
|
||||
args: args{rawHost: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"},
|
||||
want: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
|
||||
},
|
||||
{
|
||||
name: "ipv6 with port",
|
||||
args: args{rawHost: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080"},
|
||||
want: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080",
|
||||
},
|
||||
{
|
||||
name: "host with trailing colon",
|
||||
args: args{rawHost: "example.com:"},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "host with invalid port",
|
||||
args: args{rawHost: "example.com:port"},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "invalid host",
|
||||
args: args{rawHost: "localhost:@attacker.com"},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "invalid host",
|
||||
args: args{rawHost: "localhost:@attacker.com:8080"},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "empty host",
|
||||
args: args{rawHost: ""},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, sanitizeHost(tt.args.rawHost), "sanitizeHost(%v)", tt.args.rawHost)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user