Skip to content

Commit 9f93298

Browse files
committed
restrict private IP access
1 parent ac9b635 commit 9f93298

File tree

4 files changed

+71
-0
lines changed

4 files changed

+71
-0
lines changed

doc/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- (new) more auto-refresh options: 12h & 24h (thanks to @aswerkljh for suggestion)
55
- (fix) smooth scrolling on iOS (thanks to gatheraled)
66
- (etc) cookie security measures (thanks to Tom Fitzhenry)
7+
- (etc) restrict access to internal IPs for page crawler (thanks to Omar Kurt)
78

89
# v2.5 (2025-03-26)
910

src/server/routes.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,10 @@ func (s *Server) handlePageCrawl(c *router.Context) {
513513
})
514514
return
515515
}
516+
if isInternalFromURL(url) {
517+
log.Printf("attempt to access internal IP %s from %s", url, c.Req.RemoteAddr)
518+
return
519+
}
516520

517521
body, err := worker.GetBody(url)
518522
if err != nil {

src/server/util.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package server
2+
3+
import (
4+
"net"
5+
"net/url"
6+
"strings"
7+
)
8+
9+
func isInternalFromURL(urlStr string) bool {
10+
parsedURL, err := url.Parse(urlStr)
11+
if err != nil {
12+
return false
13+
}
14+
15+
host := parsedURL.Host
16+
17+
// Handle "host:port" format
18+
if strings.Contains(host, ":") {
19+
host, _, err = net.SplitHostPort(host)
20+
if err != nil {
21+
return false
22+
}
23+
}
24+
25+
if host == "localhost" {
26+
return true
27+
}
28+
29+
ip := net.ParseIP(host)
30+
if ip == nil {
31+
return false
32+
}
33+
34+
return ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast()
35+
}

src/server/util_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package server
2+
3+
import "testing"
4+
5+
func TestIsInternalFromURL(t *testing.T) {
6+
tests := []struct {
7+
url string
8+
expected bool
9+
}{
10+
{"http://192.168.1.1:8080", true},
11+
{"http://10.0.0.5", true},
12+
{"http://172.16.0.1", true},
13+
{"http://172.31.255.255", true},
14+
{"http://172.32.0.1", false}, // outside private range
15+
{"http://127.0.0.1", true},
16+
{"http://127.0.0.1:7000", true},
17+
{"http://127.0.0.1:7000/secret", true},
18+
{"http://169.254.0.5", true},
19+
{"http://localhost", true}, // resolves to 127.0.0.1
20+
{"http://8.8.8.8", false},
21+
{"http://google.com", false}, // resolves to public IPs
22+
{"invalid-url", false}, // invalid format
23+
{"", false}, // empty string
24+
}
25+
for _, test := range tests {
26+
result := isInternalFromURL(test.url)
27+
if result != test.expected {
28+
t.Errorf("isInternalFromURL(%q) = %v; want %v", test.url, result, test.expected)
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)