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:
parent
28c37f1069
commit
f7bc9fd5fb
|
|
@ -5,11 +5,28 @@ import { getCurrentUser } from '@/lib/auth'
|
||||||
import { hasPermission } from '@/lib/permissions'
|
import { hasPermission } from '@/lib/permissions'
|
||||||
import { getMaskedConfig, updateMonitorConfig } from '@/lib/monitor/settings-manager'
|
import { getMaskedConfig, updateMonitorConfig } from '@/lib/monitor/settings-manager'
|
||||||
import { writeAuditLog, getClientIP } from '@/lib/audit'
|
import { writeAuditLog, getClientIP } from '@/lib/audit'
|
||||||
|
import net from 'net'
|
||||||
|
|
||||||
// 主机名格式校验(纵深防御):只允许合法主机名/IP 字符,拒绝 URL/路径/空格等,
|
// 拒绝内网/回环/链路本地/CGNAT 的 IPv4 字面量(SSRF 纵深防御,防收信指向内网主机)
|
||||||
// 防止误配或配置被篡改后连接到任意主机(SSRF 面收窄)
|
function isPrivateIPv4(host: string): boolean {
|
||||||
const isValidHost = (v: unknown): boolean =>
|
if (!net.isIPv4(host)) return false
|
||||||
typeof v === 'string' && v.length > 0 && v.length <= 253 && /^[a-zA-Z0-9.-]+$/.test(v)
|
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> = {
|
const VALIDATORS: Record<string, (v: unknown) => boolean> = {
|
||||||
'monitor.enabled': (v) => typeof v === 'boolean',
|
'monitor.enabled': (v) => typeof v === 'boolean',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue