fix(security): SSRF guard gaps + DNS port restriction + metrics auth #112

Merged
lerko merged 1 commits from fix/security-hardening into main 2026-06-11 23:04:06 +00:00
3 changed files with 26 additions and 6 deletions
Showing only changes of commit f7da69f25f - Show all commits
+19 -4
View File
@@ -63,7 +63,7 @@ func RunCheck(ctx context.Context, site models.SiteConfig, strict, insecure *htt
case "port":
return runPortCheck(ctx, site)
case "dns":
return runDNSCheck(ctx, site)
return runDNSCheck(ctx, site, private)
default:
return CheckResult{SiteID: site.ID, Status: string(models.StatusDown), ErrorReason: "unsupported monitor type: " + site.Type}
}
@@ -180,7 +180,7 @@ func runPortCheck(_ context.Context, site models.SiteConfig) CheckResult {
return CheckResult{SiteID: site.ID, Status: string(models.StatusUp), LatencyNs: latency.Nanoseconds()}
}
func runDNSCheck(_ context.Context, site models.SiteConfig) CheckResult {
func runDNSCheck(_ context.Context, site models.SiteConfig, allowPrivate bool) CheckResult {
host := site.Hostname
if host == "" {
host = site.URL
@@ -190,9 +190,24 @@ func runDNSCheck(_ context.Context, site models.SiteConfig) CheckResult {
if server == "" {
server = defaultDNSServer
}
if _, _, err := net.SplitHostPort(server); err != nil {
server = net.JoinHostPort(server, defaultDNSPort)
serverHost, serverPort, err := net.SplitHostPort(server)
if err != nil {
serverHost = server
serverPort = defaultDNSPort
}
if !allowPrivate {
if serverPort != defaultDNSPort {
return CheckResult{SiteID: site.ID, Status: string(models.StatusDown), ErrorReason: "DNS server port must be 53"}
}
if ips, err := net.LookupIP(serverHost); err == nil {
for _, ip := range ips {
if isPrivateIP(ip) {
return CheckResult{SiteID: site.ID, Status: string(models.StatusDown), ErrorReason: "DNS server resolves to private address"}
}
}
}
}
server = net.JoinHostPort(serverHost, serverPort)
qtype := dns.TypeA
switch site.DNSResolveType {
+5
View File
@@ -11,9 +11,11 @@ var privateRanges []*net.IPNet
func init() {
cidrs := []string{
"0.0.0.0/8",
"127.0.0.0/8",
"::1/128",
"10.0.0.0/8",
"100.64.0.0/10",
"172.16.0.0/12",
"192.168.0.0/16",
"169.254.0.0/16",
@@ -27,6 +29,9 @@ func init() {
}
func isPrivateIP(ip net.IP) bool {
if ip.IsUnspecified() || ip.IsMulticast() || ip.IsLoopback() {
return true
}
for _, network := range privateRanges {
if network.Contains(ip) {
return true
+2 -2
View File
@@ -354,8 +354,8 @@ func (s *Server) handleMetrics(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if !s.cfg.MetricsPublic && s.cfg.ClusterKey != "" {
if !checkSecret(r.Header.Get("X-Upkeep-Secret"), s.cfg.ClusterKey) {
if !s.cfg.MetricsPublic {
if !s.requireAuth(r) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}