From a6a782bed4feb783b39d9e90c59a3dbd0ee885b9 Mon Sep 17 00:00:00 2001 From: fatedier Date: Sun, 9 Aug 2026 22:52:30 +0800 Subject: [PATCH] config: reject case-insensitive subdomain domains (#5474) --- pkg/config/v1/validation/proxy.go | 6 +- pkg/config/v1/validation/proxy_test.go | 76 ++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 pkg/config/v1/validation/proxy_test.go diff --git a/pkg/config/v1/validation/proxy.go b/pkg/config/v1/validation/proxy.go index 744620f0..5d430e6b 100644 --- a/pkg/config/v1/validation/proxy.go +++ b/pkg/config/v1/validation/proxy.go @@ -79,9 +79,11 @@ func validateDomainConfigForClient(c *v1.DomainConfig) error { } func validateDomainConfigForServer(c *v1.DomainConfig, s *v1.ServerConfig) error { + subDomainHost := strings.ToLower(s.SubDomainHost) for _, domain := range c.CustomDomains { - if s.SubDomainHost != "" && len(strings.Split(s.SubDomainHost, ".")) < len(strings.Split(domain, ".")) { - if strings.HasSuffix(domain, "."+s.SubDomainHost) { + canonicalDomain := strings.ToLower(domain) + if subDomainHost != "" && len(strings.Split(subDomainHost, ".")) < len(strings.Split(canonicalDomain, ".")) { + if strings.HasSuffix(canonicalDomain, "."+subDomainHost) { return fmt.Errorf("custom domain [%s] should not belong to subdomain host [%s]", domain, s.SubDomainHost) } } diff --git a/pkg/config/v1/validation/proxy_test.go b/pkg/config/v1/validation/proxy_test.go new file mode 100644 index 00000000..b9411dc8 --- /dev/null +++ b/pkg/config/v1/validation/proxy_test.go @@ -0,0 +1,76 @@ +// Copyright 2026 The frp Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package validation + +import ( + "testing" + + "github.com/stretchr/testify/require" + + v1 "github.com/fatedier/frp/pkg/config/v1" +) + +func TestValidateDomainConfigForServerRejectsSubdomainHostCaseInsensitively(t *testing.T) { + tests := []struct { + name string + subDomainHost string + customDomain string + wantErr bool + }{ + { + name: "lowercase subdomain", + subDomainHost: "frp.example.com", + customDomain: "victim.frp.example.com", + wantErr: true, + }, + { + name: "mixed case custom domain", + subDomainHost: "frp.example.com", + customDomain: "victim.FRP.example.com", + wantErr: true, + }, + { + name: "mixed case wildcard domain", + subDomainHost: "frp.example.com", + customDomain: "*.FRP.example.com", + wantErr: true, + }, + { + name: "mixed case subdomain host", + subDomainHost: "FRP.Example.Com", + customDomain: "victim.frp.example.com", + wantErr: true, + }, + { + name: "external domain", + subDomainHost: "frp.example.com", + customDomain: "victim.example.net", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateDomainConfigForServer( + &v1.DomainConfig{CustomDomains: []string{tt.customDomain}}, + &v1.ServerConfig{SubDomainHost: tt.subDomainHost}, + ) + if tt.wantErr { + require.ErrorContains(t, err, "should not belong to subdomain host") + return + } + require.NoError(t, err) + }) + } +}