安全加固:Web 登录防爆破(IP+用户名双维度限速 5次/分钟锁10分钟,未知用户名 dummy bcrypt 防枚举),瓦片代理 SSRF 加固(DialContext 拒绝内网/链路本地/CGNAT/ULA 与重定向限制,Content-Type image 白名单+nosniff),/api/discard-details 公开去敏(新增 admin 全字段端点),公开地图源接口外部 URL/key 一律代理化,install.sh 随机管理员密码+非回环默认口令拒绝启动,后端 v1.4.0

This commit is contained in:
2026-08-20 17:00:41 +08:00
parent fe0cf036cf
commit f21e8337af
13 changed files with 658 additions and 48 deletions
+83
View File
@@ -0,0 +1,83 @@
package web
import (
"context"
"net/netip"
"testing"
)
func TestIsPublicAddr(t *testing.T) {
cases := []struct {
ip string
allow bool
}{
{"8.8.8.8", true},
{"1.1.1.1", true},
{"2001:4860:4860::8888", true},
{"127.0.0.1", false},
{"::1", false},
{"10.0.0.1", false},
{"172.16.5.4", false},
{"192.168.1.1", false},
{"169.254.169.254", false}, // AWS metadata
{"100.64.0.1", false}, // CGNAT
{"100.127.255.254", false}, // CGNAT
{"224.0.0.1", false}, // multicast
{"0.0.0.0", false},
{"fc00::1", false}, // ULA
{"fe80::1", false}, // link-local IPv6
{"", false},
}
for _, tc := range cases {
addr, err := netip.ParseAddr(tc.ip)
got := false
if err == nil {
got = isPublicAddr(addr)
}
if got != tc.allow {
t.Errorf("isPublicAddr(%q)=%v, want %v", tc.ip, got, tc.allow)
}
}
}
func TestIsMapTileDialTargetAllowed(t *testing.T) {
cases := []struct {
network string
addr string
allow bool
}{
{"tcp", "8.8.8.8:443", true},
{"tcp4", "1.1.1.1:80", true},
{"tcp", "127.0.0.1:80", false},
{"tcp", "10.0.0.1:80", false},
{"tcp", "169.254.169.254:80", false},
{"tcp", "100.64.1.1:80", false},
{"tcp6", "[::1]:1883", false},
{"unix", "/tmp/x.sock", false},
{"tcp", "no-such-host.invalid:80", false},
{"tcp", "localhost:80", false}, // 解析到 127.0.0.1
}
for _, tc := range cases {
if got := isMapTileDialTargetAllowed(context.Background(), tc.network, tc.addr); got != tc.allow {
t.Errorf("isMapTileDialTargetAllowed(%q, %q)=%v, want %v", tc.network, tc.addr, got, tc.allow)
}
}
}
func TestMapTileContentType(t *testing.T) {
png := []byte{0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a}
if got := mapTileContentType(png); got != "image/png" {
t.Errorf("png: got %q", got)
}
html := []byte("<html><script>alert(1)</script></html>")
if got := mapTileContentType(html); got != "application/octet-stream" {
t.Errorf("html must be blocked, got %q", got)
}
svg := []byte("<svg xmlns='http://www.w3.org/2000/svg'></svg>")
if got := mapTileContentType(svg); got != "application/octet-stream" {
t.Errorf("svg must be blocked, got %q", got)
}
}
func TestIsLoopbackHost(t *testing.T) {
}