Skip to content

Commit d600edd

Browse files
committed
UI/Prom: Remove sanitizeString, and use proper escapes where needed
Fixes #2885 Fixes #2886
1 parent 2a1d678 commit d600edd

1 file changed

Lines changed: 20 additions & 35 deletions

File tree

dnscrypt-proxy/monitoring_ui.go

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,6 @@ import (
1818
"github.com/miekg/dns"
1919
)
2020

21-
// sanitizeString - Sanitizes user input to prevent XSS attacks
22-
func sanitizeString(input string) string {
23-
// HTML escape to prevent XSS
24-
escaped := html.EscapeString(input)
25-
// Additional validation for domain names - only allow valid domain characters
26-
if strings.Contains(input, ".") { // Likely a domain name
27-
// Remove any non-domain characters
28-
var result strings.Builder
29-
for _, r := range escaped {
30-
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') ||
31-
(r >= '0' && r <= '9') || r == '.' || r == '-' || r == '_' {
32-
result.WriteRune(r)
33-
}
34-
}
35-
return result.String()
36-
}
37-
return escaped
38-
}
39-
4021
// MonitoringUIConfig - Configuration for the monitoring UI
4122
type MonitoringUIConfig struct {
4223
Enabled bool `toml:"enabled"`
@@ -360,10 +341,11 @@ func (ui *MonitoringUI) UpdateMetrics(pluginsState PluginsState, msg *dns.Msg, s
360341

361342
// Update top domains - separate lock
362343
if mc.privacyLevel < 2 {
363-
sanitizedDomain := sanitizeString(pluginsState.qName)
344+
// Store domain name directly - no sanitization needed for internal metrics
345+
domainName := pluginsState.qName
364346
mc.domainMutex.Lock()
365-
mc.topDomains[sanitizedDomain]++
366-
dlog.Debugf("Domain %s, count: %d", sanitizedDomain, mc.topDomains[sanitizedDomain])
347+
mc.topDomains[domainName]++
348+
dlog.Debugf("Domain %s, count: %d", domainName, mc.topDomains[domainName])
367349
mc.domainMutex.Unlock()
368350
}
369351

@@ -410,13 +392,14 @@ func (ui *MonitoringUI) UpdateMetrics(pluginsState PluginsState, msg *dns.Msg, s
410392
}
411393

412394
entry := QueryLogEntry{
413-
Timestamp: now,
414-
ClientIP: clientIP,
415-
Domain: sanitizeString(pluginsState.qName),
416-
Type: sanitizeString(qType),
417-
ResponseCode: sanitizeString(returnCode),
395+
Timestamp: now,
396+
ClientIP: clientIP,
397+
// HTML escape only the fields that will be displayed in web UI
398+
Domain: html.EscapeString(pluginsState.qName),
399+
Type: qType, // DNS types are safe, no escaping needed
400+
ResponseCode: returnCode, // DNS response codes are safe, no escaping needed
418401
ResponseTime: responseTime,
419-
Server: sanitizeString(pluginsState.serverName),
402+
Server: html.EscapeString(pluginsState.serverName),
420403
CacheHit: pluginsState.cacheHit,
421404
}
422405

@@ -527,17 +510,19 @@ func (mc *MetricsCollector) generatePrometheusMetrics() string {
527510
result.WriteString("# HELP dnscrypt_proxy_server_queries_total Total queries per server\n")
528511
result.WriteString("# TYPE dnscrypt_proxy_server_queries_total counter\n")
529512
for server, count := range mc.serverQueryCount {
530-
sanitizedServer := sanitizeString(server)
531-
result.WriteString(fmt.Sprintf("dnscrypt_proxy_server_queries_total{server=\"%s\"} %d\n", sanitizedServer, count))
513+
// For Prometheus labels, escape quotes and backslashes to prevent label injection
514+
escapedServer := strings.ReplaceAll(strings.ReplaceAll(server, "\\", "\\\\"), "\"", "\\\"")
515+
result.WriteString(fmt.Sprintf("dnscrypt_proxy_server_queries_total{server=\"%s\"} %d\n", escapedServer, count))
532516
}
533517

534518
result.WriteString("# HELP dnscrypt_proxy_server_response_time_average_ms Average response time per server in milliseconds\n")
535519
result.WriteString("# TYPE dnscrypt_proxy_server_response_time_average_ms gauge\n")
536520
for server, count := range mc.serverQueryCount {
537521
if count > 0 {
538522
avgTime := float64(mc.serverResponseTime[server]) / float64(count)
539-
sanitizedServer := sanitizeString(server)
540-
result.WriteString(fmt.Sprintf("dnscrypt_proxy_server_response_time_average_ms{server=\"%s\"} %.2f\n", sanitizedServer, avgTime))
523+
// For Prometheus labels, escape quotes and backslashes to prevent label injection
524+
escapedServer := strings.ReplaceAll(strings.ReplaceAll(server, "\\", "\\\\"), "\"", "\\\"")
525+
result.WriteString(fmt.Sprintf("dnscrypt_proxy_server_response_time_average_ms{server=\"%s\"} %.2f\n", escapedServer, avgTime))
541526
}
542527
}
543528
mc.serverMutex.RUnlock()
@@ -547,8 +532,8 @@ func (mc *MetricsCollector) generatePrometheusMetrics() string {
547532
result.WriteString("# HELP dnscrypt_proxy_query_type_total Total queries per DNS record type\n")
548533
result.WriteString("# TYPE dnscrypt_proxy_query_type_total counter\n")
549534
for qtype, count := range mc.queryTypes {
550-
sanitizedQtype := sanitizeString(qtype)
551-
result.WriteString(fmt.Sprintf("dnscrypt_proxy_query_type_total{type=\"%s\"} %d\n", sanitizedQtype, count))
535+
// DNS query types are safe alphanumeric values, no escaping needed
536+
result.WriteString(fmt.Sprintf("dnscrypt_proxy_query_type_total{type=\"%s\"} %d\n", qtype, count))
552537
}
553538
mc.queryTypesMutex.RUnlock()
554539

@@ -688,7 +673,7 @@ func (mc *MetricsCollector) GetMetrics() map[string]interface{} {
688673
count := 0
689674
for _, dc := range domainCounts {
690675
topDomainsList = append(topDomainsList, map[string]interface{}{
691-
"domain": sanitizeString(dc.domain),
676+
"domain": html.EscapeString(dc.domain),
692677
"count": dc.count,
693678
})
694679
count++

0 commit comments

Comments
 (0)