From 1b7585d54a800b51915a0393d239f4b29356d635 Mon Sep 17 00:00:00 2001 From: yuyx <237899745@qq.com> Date: Thu, 27 Nov 2025 14:56:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(security):=20=E5=AE=8C=E5=96=84SSRF?= =?UTF-8?q?=E9=98=B2=E6=8A=A4=E7=9A=84IP=E5=9C=B0=E5=9D=80=E6=A3=80?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 完整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 --- backend/server.js | 68 +++++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/backend/server.js b/backend/server.js index fad85ad..6589201 100644 --- a/backend/server.js +++ b/backend/server.js @@ -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) {