From 9f93298cf9f1fecbb20bc4e314b3b5be134625be Mon Sep 17 00:00:00 2001 From: nkanaev Date: Thu, 2 Oct 2025 10:16:35 +0100 Subject: [PATCH] restrict private IP access --- doc/changelog.md | 1 + src/server/routes.go | 4 ++++ src/server/util.go | 35 +++++++++++++++++++++++++++++++++++ src/server/util_test.go | 31 +++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 src/server/util.go create mode 100644 src/server/util_test.go diff --git a/doc/changelog.md b/doc/changelog.md index e0f1f19..55efe0f 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -4,6 +4,7 @@ - (new) more auto-refresh options: 12h & 24h (thanks to @aswerkljh for suggestion) - (fix) smooth scrolling on iOS (thanks to gatheraled) - (etc) cookie security measures (thanks to Tom Fitzhenry) +- (etc) restrict access to internal IPs for page crawler (thanks to Omar Kurt) # v2.5 (2025-03-26) diff --git a/src/server/routes.go b/src/server/routes.go index e1913a8..9f12a1c 100644 --- a/src/server/routes.go +++ b/src/server/routes.go @@ -513,6 +513,10 @@ func (s *Server) handlePageCrawl(c *router.Context) { }) return } + if isInternalFromURL(url) { + log.Printf("attempt to access internal IP %s from %s", url, c.Req.RemoteAddr) + return + } body, err := worker.GetBody(url) if err != nil { diff --git a/src/server/util.go b/src/server/util.go new file mode 100644 index 0000000..6cb2457 --- /dev/null +++ b/src/server/util.go @@ -0,0 +1,35 @@ +package server + +import ( + "net" + "net/url" + "strings" +) + +func isInternalFromURL(urlStr string) bool { + parsedURL, err := url.Parse(urlStr) + if err != nil { + return false + } + + host := parsedURL.Host + + // Handle "host:port" format + if strings.Contains(host, ":") { + host, _, err = net.SplitHostPort(host) + if err != nil { + return false + } + } + + if host == "localhost" { + return true + } + + ip := net.ParseIP(host) + if ip == nil { + return false + } + + return ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() +} diff --git a/src/server/util_test.go b/src/server/util_test.go new file mode 100644 index 0000000..b863001 --- /dev/null +++ b/src/server/util_test.go @@ -0,0 +1,31 @@ +package server + +import "testing" + +func TestIsInternalFromURL(t *testing.T) { + tests := []struct { + url string + expected bool + }{ + {"http://192.168.1.1:8080", true}, + {"http://10.0.0.5", true}, + {"http://172.16.0.1", true}, + {"http://172.31.255.255", true}, + {"http://172.32.0.1", false}, // outside private range + {"http://127.0.0.1", true}, + {"http://127.0.0.1:7000", true}, + {"http://127.0.0.1:7000/secret", true}, + {"http://169.254.0.5", true}, + {"http://localhost", true}, // resolves to 127.0.0.1 + {"http://8.8.8.8", false}, + {"http://google.com", false}, // resolves to public IPs + {"invalid-url", false}, // invalid format + {"", false}, // empty string + } + for _, test := range tests { + result := isInternalFromURL(test.url) + if result != test.expected { + t.Errorf("isInternalFromURL(%q) = %v; want %v", test.url, result, test.expected) + } + } +} \ No newline at end of file