diff --git a/src/services/balanceScriptService.js b/src/services/balanceScriptService.js index 5bf06801..3d348d33 100644 --- a/src/services/balanceScriptService.js +++ b/src/services/balanceScriptService.js @@ -2,6 +2,50 @@ const vm = require('vm') const axios = require('axios') const { isBalanceScriptEnabled } = require('../utils/featureFlags') +/** + * SSRF防护:检查URL是否访问内网或敏感地址 + * @param {string} url - 要检查的URL + * @returns {boolean} - true表示URL安全 + */ +function isUrlSafe(url) { + try { + const parsed = new URL(url) + const hostname = parsed.hostname.toLowerCase() + + // 禁止的协议 + if (!['http:', 'https:'].includes(parsed.protocol)) { + return false + } + + // 禁止访问localhost和私有IP + const privatePatterns = [ + /^localhost$/i, + /^127\./, + /^10\./, + /^172\.(1[6-9]|2[0-9]|3[0-1])\./, + /^192\.168\./, + /^169\.254\./, // AWS metadata + /^0\./, // 0.0.0.0 + /^::1$/, + /^fc00:/i, + /^fe80:/i, + /\.local$/i, + /\.internal$/i, + /\.localhost$/i + ] + + for (const pattern of privatePatterns) { + if (pattern.test(hostname)) { + return false + } + } + + return true + } catch { + return false + } +} + /** * 可配置脚本余额查询执行器 * - 脚本格式:({ request: {...}, extractor: function(response){...} }) @@ -55,6 +99,11 @@ class BalanceScriptService { throw new Error('脚本 request.url 不能为空') } + // SSRF防护:验证URL安全性 + if (!isUrlSafe(request.url)) { + throw new Error('脚本 request.url 不安全:禁止访问内网地址、localhost或使用非HTTP(S)协议') + } + if (typeof extractor !== 'function') { throw new Error('脚本 extractor 必须是函数') } diff --git a/src/utils/featureFlags.js b/src/utils/featureFlags.js index 35802d55..c2dd4f07 100644 --- a/src/utils/featureFlags.js +++ b/src/utils/featureFlags.js @@ -20,8 +20,9 @@ const parseBooleanEnv = (value) => { } /** - * 是否允许执行“余额脚本”(安全开关) - * 默认开启,便于保持现有行为;如需禁用请显式设置 BALANCE_SCRIPT_ENABLED=false(环境变量优先) + * 是否允许执行"余额脚本"(安全开关) + * ⚠️ 安全警告:vm模块非安全沙箱,默认禁用。如需启用请显式设置 BALANCE_SCRIPT_ENABLED=true + * 仅在完全信任管理员且了解RCE风险时才启用此功能 */ const isBalanceScriptEnabled = () => { if ( @@ -36,7 +37,8 @@ const isBalanceScriptEnabled = () => { config?.features?.balanceScriptEnabled ?? config?.security?.enableBalanceScript - return typeof fromConfig === 'boolean' ? fromConfig : true + // 默认禁用,需显式启用 + return typeof fromConfig === 'boolean' ? fromConfig : false } module.exports = {