Skip to content

Commit f344653

Browse files
committed
fix: domain-specific TLS fingerprinting check at handshake level
Fixed issue where users were banned for 'Malicious TLS fingerprint' even when 'Analyze TLS fingerprints' was disabled in UI for their domain. Root cause: IsTLSFingerprintEnabled() returned true if ANY domain had the feature enabled, and this boolean was captured at server startup. The TLS handshake callback would then check fingerprints for ALL domains. Changes: - Modified GetConfigForClientWrapper to accept a domain lookup function instead of a global boolean - Added IsTLSFingerprintEnabledForDomain() method to ConfigManager for per-domain configuration checks - Updated proxy/https.go to pass domain-specific lookup function - Fixed DNS test mock to include missing GetAgents() method Now TLS fingerprint checking only occurs for domains that explicitly have the feature enabled in their L7 protection settings.
1 parent a41e6ba commit f344653

4 files changed

Lines changed: 43 additions & 5 deletions

File tree

config/manager.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,32 @@ func (cm *ConfigManager) IsTLSFingerprintEnabled() bool {
472472
return false
473473
}
474474

475+
// IsTLSFingerprintEnabledForDomain checks if TLS fingerprinting is enabled for a specific domain
476+
// This allows per-domain TLS fingerprinting configuration at the TLS handshake level
477+
func (cm *ConfigManager) IsTLSFingerprintEnabledForDomain(domain string) bool {
478+
cm.mu.RLock()
479+
defer cm.mu.RUnlock()
480+
481+
if domain == "" {
482+
return false
483+
}
484+
485+
// Normalize domain to lowercase for comparison
486+
domainLower := strings.ToLower(domain)
487+
488+
for _, d := range cm.config.Domains {
489+
// Check exact match or subdomain match
490+
domainConfigLower := strings.ToLower(d.Domain)
491+
if domainConfigLower == domainLower || strings.HasSuffix(domainLower, "."+domainConfigLower) {
492+
if d.HTTPProxy.AntiDDoS != nil && d.HTTPProxy.AntiDDoS.L7Protection != nil {
493+
return d.HTTPProxy.AntiDDoS.L7Protection.TLSFingerprintEnabled
494+
}
495+
return false
496+
}
497+
}
498+
return false
499+
}
500+
475501
func (cm *ConfigManager) GetAgentID() string {
476502
cm.mu.RLock()
477503
defer cm.mu.RUnlock()

dns/server_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ func (m *MockConfigManager) GetAgentIP() string {
3333
return m.agentIP
3434
}
3535

36+
func (m *MockConfigManager) GetAgents() []config.FallbackAgentInfo {
37+
return []config.FallbackAgentInfo{}
38+
}
39+
3640
func TestCNAMEFlattening(t *testing.T) {
3741
tests := []struct {
3842
name string

firewall/tls_handshake.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ func GetConfigForClientWrapper(
259259
baseConfig *tls.Config,
260260
configuredDomains []string,
261261
getCertificateFunc func(*tls.ClientHelloInfo) (*tls.Certificate, error),
262-
tlsFingerprintEnabled bool,
262+
isTLSEnabledForDomainFunc func(domain string) bool,
263263
) func(*tls.ClientHelloInfo) (*tls.Config, error) {
264264

265265
handshakeLimiter := GetHandshakeRateLimiter()
@@ -269,7 +269,7 @@ func GetConfigForClientWrapper(
269269
ip := ExtractIPFromAddr(hello.Conn.RemoteAddr())
270270

271271
// === FORTRESS LAYER 1: SNI Validation (Cheap Check) ===
272-
// Отсекаем сканеры и боты без валидного SNI
272+
// Отсекаем сканеры и ботов без валидного SNI
273273
if err := ValidateSNI(hello.ServerName, configuredDomains); err != nil {
274274
// Логируем с указанием настроенных доменов для отладки
275275
log.Printf("[TLS-Fortress] Blocked handshake from %s: %v (SNI: %s, configured domains: %d)",
@@ -308,7 +308,13 @@ func GetConfigForClientWrapper(
308308
}
309309

310310
// === FORTRESS LAYER 3: TLS Fingerprinting (JA3/JA4) ===
311-
// Анализ TLS fingerprint для обнаружения ботов (только если включено в настройках)
311+
// Анализ TLS fingerprint для обнаружения ботов (только если включено для конкретного домена)
312+
// Проверяем динамически для каждого домена, а не глобально
313+
tlsFingerprintEnabled := false
314+
if isTLSEnabledForDomainFunc != nil && hello.ServerName != "" {
315+
tlsFingerprintEnabled = isTLSEnabledForDomainFunc(hello.ServerName)
316+
}
317+
312318
if tlsFingerprintEnabled {
313319
tlsFingerprint := ExtractTLSFingerprint(hello)
314320
if tlsFingerprint != "" {

proxy/https.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,14 @@ func StartHTTPSProxy(configMgr *config.ConfigManager) {
7777
// === FORTRESS EDITION: GetConfigForClient - главная точка защиты ===
7878
// Layer 1: SNI Validation (отсекает сканеры)
7979
// Layer 2: Handshake Rate Limiting (защита от velocity attacks)
80-
// Layer 3: TLS Fingerprinting (обнаружение ботнетов)
80+
// Layer 3: TLS Fingerprinting (обнаружение ботнетов) - домен-специфичная проверка
8181
GetConfigForClient: firewall.GetConfigForClientWrapper(
8282
nil, // baseConfig (nil = use default)
8383
configuredDomains,
8484
server.getCertificate,
85-
server.configMgr.IsTLSFingerprintEnabled(),
85+
func(domain string) bool {
86+
return server.configMgr.IsTLSFingerprintEnabledForDomain(domain)
87+
},
8688
),
8789
}
8890

0 commit comments

Comments
 (0)