Implement three-layer SSRF protection: - Layer 1: URL pre-validation (existing) - Layer 2: network.NewSafeHTTPClient() with connection-time IP validation - Layer 3: Redirect target validation New package: internal/network/safeclient.go - IsPrivateIP(): Blocks RFC 1918, loopback, link-local (169.254.x.x), reserved ranges, IPv6 private - safeDialer(): DNS resolve → validate all IPs → dial validated IP (prevents DNS rebinding/TOCTOU) - NewSafeHTTPClient(): Functional options (WithTimeout, WithAllowLocalhost, WithAllowedDomains, WithMaxRedirects) Updated services: - notification_service.go - security_notification_service.go - update_service.go - crowdsec/registration.go (WithAllowLocalhost for LAPI) - crowdsec/hub_sync.go (WithAllowedDomains for CrowdSec domains) Consolidated duplicate isPrivateIP implementations to use network package. Test coverage: 90.9% for network package CodeQL: 0 SSRF findings (CWE-918 mitigated) Closes #450
53 lines
1.3 KiB
Go
53 lines
1.3 KiB
Go
package utils
|
|
|
|
import (
|
|
"net"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/network"
|
|
)
|
|
|
|
// IsPrivateIP checks if the given host string is a private IPv4 address.
|
|
// Returns false for hostnames, invalid IPs, or public IP addresses.
|
|
//
|
|
// Deprecated: This function only checks IPv4. For comprehensive SSRF protection,
|
|
// use network.IsPrivateIP() directly which handles IPv4, IPv6, and IPv4-mapped IPv6.
|
|
func IsPrivateIP(host string) bool {
|
|
ip := net.ParseIP(host)
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
|
|
// Ensure it's IPv4 (for backward compatibility)
|
|
ip4 := ip.To4()
|
|
if ip4 == nil {
|
|
return false
|
|
}
|
|
|
|
// Use centralized network.IsPrivateIP for consistent checking
|
|
return network.IsPrivateIP(ip)
|
|
}
|
|
|
|
// IsDockerBridgeIP checks if the given host string is likely a Docker bridge network IP.
|
|
// Docker typically uses 172.17.x.x for the default bridge and 172.18-31.x.x for user-defined networks.
|
|
// Returns false for hostnames, invalid IPs, or non-Docker IP addresses.
|
|
func IsDockerBridgeIP(host string) bool {
|
|
ip := net.ParseIP(host)
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
|
|
// Ensure it's IPv4
|
|
ip4 := ip.To4()
|
|
if ip4 == nil {
|
|
return false
|
|
}
|
|
|
|
// Docker bridge network CIDR range: 172.16.0.0/12
|
|
_, dockerNetwork, err := net.ParseCIDR("172.16.0.0/12")
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return dockerNetwork.Contains(ip4)
|
|
}
|