fix: SSRF 加固增强 — 拒绝内网/回环/链路本地 IPv4 邮箱服务器地址

响应 push 安全审查:settings VALIDATORS 的 isValidHost 增加内网 IPv4 字面量
检测,拒绝 0/8、10/8、127/8(回环)、169.254/16(链路本地/云元数据)、
172.16/12、192.168/16、100.64/10(CGNAT),防止 imap/pop3/smtp 服务器地址
被指向内网主机(SSRF 纵深防御)。

未做 DNS 解析 + rebinding 防御:host 需 monitor:write 权限修改、且为 POP3/TLS
协议连接(非 HTTP),对 admin-only 邮箱配置属过度设计。IPv6 字面量含 ':'
已被字符集正则天然拒绝。
This commit is contained in:
gitadmin 2026-07-13 09:17:01 +08:00
parent 28c37f1069
commit f7bc9fd5fb
1 changed files with 21 additions and 4 deletions

View File

@ -5,11 +5,28 @@ import { getCurrentUser } from '@/lib/auth'
import { hasPermission } from '@/lib/permissions'
import { getMaskedConfig, updateMonitorConfig } from '@/lib/monitor/settings-manager'
import { writeAuditLog, getClientIP } from '@/lib/audit'
import net from 'net'
// 主机名格式校验(纵深防御):只允许合法主机名/IP 字符,拒绝 URL/路径/空格等,
// 防止误配或配置被篡改后连接到任意主机SSRF 面收窄)
const isValidHost = (v: unknown): boolean =>
typeof v === 'string' && v.length > 0 && v.length <= 253 && /^[a-zA-Z0-9.-]+$/.test(v)
// 拒绝内网/回环/链路本地/CGNAT 的 IPv4 字面量SSRF 纵深防御,防收信指向内网主机)
function isPrivateIPv4(host: string): boolean {
if (!net.isIPv4(host)) return false
const p = host.split('.').map(Number)
if (p[0] === 0 || p[0] === 10 || p[0] === 127) return true // 0/8, 10/8, 回环 127/8
if (p[0] === 169 && p[1] === 254) return true // 链路本地 169.254/16
if (p[0] === 172 && p[1] >= 16 && p[1] <= 31) return true // 172.16/12
if (p[0] === 192 && p[1] === 168) return true // 192.168/16
if (p[0] === 100 && p[1] >= 64 && p[1] <= 127) return true // CGNAT 100.64/10
return false
}
// 主机名格式校验(纵深防御):只允许合法主机名/IP 字符IPv6 字面量含 ':' 天然被拒),
// 拒绝 URL/路径/空格及内网 IPv4收窄 SSRF 面
const isValidHost = (v: unknown): boolean => {
if (typeof v !== 'string' || v.length === 0 || v.length > 253) return false
if (!/^[a-zA-Z0-9.-]+$/.test(v)) return false
if (isPrivateIPv4(v)) return false
return true
}
const VALIDATORS: Record<string, (v: unknown) => boolean> = {
'monitor.enabled': (v) => typeof v === 'boolean',