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:
@@ -814,29 +814,51 @@ function cleanupOldTempFiles() {
|
|||||||
|
|
||||||
function isPrivateIp(ip) {
|
function isPrivateIp(ip) {
|
||||||
if (!net.isIP(ip)) return false;
|
if (!net.isIP(ip)) return false;
|
||||||
return ip.startsWith('10.') ||
|
|
||||||
ip.startsWith('192.168.') ||
|
// IPv4 私有地址和特殊地址
|
||||||
ip.startsWith('172.16.') ||
|
if (net.isIPv4(ip)) {
|
||||||
ip.startsWith('172.17.') ||
|
// 10.0.0.0/8 - 私有网络
|
||||||
ip.startsWith('172.18.') ||
|
if (ip.startsWith('10.')) return true;
|
||||||
ip.startsWith('172.19.') ||
|
// 192.168.0.0/16 - 私有网络
|
||||||
ip.startsWith('172.20.') ||
|
if (ip.startsWith('192.168.')) return true;
|
||||||
ip.startsWith('172.21.') ||
|
// 172.16.0.0/12 - 私有网络 (172.16.0.0 - 172.31.255.255)
|
||||||
ip.startsWith('172.22.') ||
|
if (/^172\.(1[6-9]|2[0-9]|3[01])\./.test(ip)) return true;
|
||||||
ip.startsWith('172.23.') ||
|
// 127.0.0.0/8 - 回环地址
|
||||||
ip.startsWith('172.24.') ||
|
if (ip.startsWith('127.')) return true;
|
||||||
ip.startsWith('172.25.') ||
|
// 0.0.0.0 - 无效地址
|
||||||
ip.startsWith('172.26.') ||
|
if (ip === '0.0.0.0') return true;
|
||||||
ip.startsWith('172.27.') ||
|
// 169.254.0.0/16 - 链路本地地址(包括云服务元数据 169.254.169.254)
|
||||||
ip.startsWith('172.28.') ||
|
if (ip.startsWith('169.254.')) return true;
|
||||||
ip.startsWith('172.29.') ||
|
// 100.64.0.0/10 - 运营商级NAT
|
||||||
ip.startsWith('172.30.') ||
|
if (/^100\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\./.test(ip)) return true;
|
||||||
ip.startsWith('172.31.') ||
|
|
||||||
ip.startsWith('127.') ||
|
return false;
|
||||||
ip === '0.0.0.0' ||
|
}
|
||||||
ip === '::1' ||
|
|
||||||
ip.startsWith('fe80:') ||
|
// IPv6 私有地址和特殊地址
|
||||||
ip.startsWith('fd');
|
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) {
|
async function validateSftpDestination(host, port) {
|
||||||
|
|||||||
Reference in New Issue
Block a user