fix(security): SSRF guard gaps + DNS port restriction + metrics auth #112
@@ -63,7 +63,7 @@ func RunCheck(ctx context.Context, site models.SiteConfig, strict, insecure *htt
|
|||||||
case "port":
|
case "port":
|
||||||
return runPortCheck(ctx, site)
|
return runPortCheck(ctx, site)
|
||||||
case "dns":
|
case "dns":
|
||||||
return runDNSCheck(ctx, site)
|
return runDNSCheck(ctx, site, private)
|
||||||
default:
|
default:
|
||||||
return CheckResult{SiteID: site.ID, Status: string(models.StatusDown), ErrorReason: "unsupported monitor type: " + site.Type}
|
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()}
|
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
|
host := site.Hostname
|
||||||
if host == "" {
|
if host == "" {
|
||||||
host = site.URL
|
host = site.URL
|
||||||
@@ -190,9 +190,24 @@ func runDNSCheck(_ context.Context, site models.SiteConfig) CheckResult {
|
|||||||
if server == "" {
|
if server == "" {
|
||||||
server = defaultDNSServer
|
server = defaultDNSServer
|
||||||
}
|
}
|
||||||
if _, _, err := net.SplitHostPort(server); err != nil {
|
serverHost, serverPort, err := net.SplitHostPort(server)
|
||||||
server = net.JoinHostPort(server, defaultDNSPort)
|
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
|
qtype := dns.TypeA
|
||||||
switch site.DNSResolveType {
|
switch site.DNSResolveType {
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ var privateRanges []*net.IPNet
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cidrs := []string{
|
cidrs := []string{
|
||||||
|
"0.0.0.0/8",
|
||||||
"127.0.0.0/8",
|
"127.0.0.0/8",
|
||||||
"::1/128",
|
"::1/128",
|
||||||
"10.0.0.0/8",
|
"10.0.0.0/8",
|
||||||
|
"100.64.0.0/10",
|
||||||
"172.16.0.0/12",
|
"172.16.0.0/12",
|
||||||
"192.168.0.0/16",
|
"192.168.0.0/16",
|
||||||
"169.254.0.0/16",
|
"169.254.0.0/16",
|
||||||
@@ -27,6 +29,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isPrivateIP(ip net.IP) bool {
|
func isPrivateIP(ip net.IP) bool {
|
||||||
|
if ip.IsUnspecified() || ip.IsMulticast() || ip.IsLoopback() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
for _, network := range privateRanges {
|
for _, network := range privateRanges {
|
||||||
if network.Contains(ip) {
|
if network.Contains(ip) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -354,8 +354,8 @@ func (s *Server) handleMetrics(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !s.cfg.MetricsPublic && s.cfg.ClusterKey != "" {
|
if !s.cfg.MetricsPublic {
|
||||||
if !checkSecret(r.Header.Get("X-Upkeep-Secret"), s.cfg.ClusterKey) {
|
if !s.requireAuth(r) {
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user