fix(security): 完善SSRF防护的IP地址检查

- 完整IPv6私有地址检查:fc00::/7, fe80::/10, ff00::/8
- 添加云服务元数据地址检查:169.254.0.0/16
- 添加运营商级NAT地址检查:100.64.0.0/10
- 支持IPv4映射的IPv6地址递归检查
- 使用正则表达式优化172.16.0.0/12检查
- 代码结构更清晰,按IPv4/IPv6分类处理

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-27 14:56:42 +08:00
parent 6305129e4f
commit 1b7585d54a

View File

@@ -814,29 +814,51 @@ function cleanupOldTempFiles() {
function isPrivateIp(ip) {
if (!net.isIP(ip)) return false;
return ip.startsWith('10.') ||
ip.startsWith('192.168.') ||
ip.startsWith('172.16.') ||
ip.startsWith('172.17.') ||
ip.startsWith('172.18.') ||
ip.startsWith('172.19.') ||
ip.startsWith('172.20.') ||
ip.startsWith('172.21.') ||
ip.startsWith('172.22.') ||
ip.startsWith('172.23.') ||
ip.startsWith('172.24.') ||
ip.startsWith('172.25.') ||
ip.startsWith('172.26.') ||
ip.startsWith('172.27.') ||
ip.startsWith('172.28.') ||
ip.startsWith('172.29.') ||
ip.startsWith('172.30.') ||
ip.startsWith('172.31.') ||
ip.startsWith('127.') ||
ip === '0.0.0.0' ||
ip === '::1' ||
ip.startsWith('fe80:') ||
ip.startsWith('fd');
// IPv4 私有地址和特殊地址
if (net.isIPv4(ip)) {
// 10.0.0.0/8 - 私有网络
if (ip.startsWith('10.')) return true;
// 192.168.0.0/16 - 私有网络
if (ip.startsWith('192.168.')) return true;
// 172.16.0.0/12 - 私有网络 (172.16.0.0 - 172.31.255.255)
if (/^172\.(1[6-9]|2[0-9]|3[01])\./.test(ip)) return true;
// 127.0.0.0/8 - 回环地址
if (ip.startsWith('127.')) return true;
// 0.0.0.0 - 无效地址
if (ip === '0.0.0.0') return true;
// 169.254.0.0/16 - 链路本地地址(包括云服务元数据 169.254.169.254
if (ip.startsWith('169.254.')) return true;
// 100.64.0.0/10 - 运营商级NAT
if (/^100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\./.test(ip)) return true;
return false;
}
// IPv6 私有地址和特殊地址
if (net.isIPv6(ip)) {
const lowerIp = ip.toLowerCase();
// ::1 - 回环地址
if (lowerIp === '::1') return true;
// :: - 未指定地址
if (lowerIp === '::') return true;
// fe80::/10 - 链路本地地址
if (/^fe[89ab][0-9a-f]:/i.test(lowerIp)) return true;
// fc00::/7 - 唯一本地地址 (fc 和 fd 开头)
if (/^f[cd][0-9a-f]{2}:/i.test(lowerIp)) return true;
// ff00::/8 - 多播地址
if (lowerIp.startsWith('ff')) return true;
// ::ffff:x.x.x.x - IPv4映射地址需要检查内部的IPv4
const ipv4Mapped = lowerIp.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
if (ipv4Mapped) {
return isPrivateIp(ipv4Mapped[1]);
}
return false;
}
return false;
}
async function validateSftpDestination(host, port) {