mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
fix(security): 修复余额脚本功能的RCE和SSRF漏洞
- 将 BALANCE_SCRIPT_ENABLED 默认值改为 false,需显式启用 - 添加 isUrlSafe() SSRF防护,禁止访问: - localhost/127.x - 私有IP (10.x, 172.16-31.x, 192.168.x) - AWS metadata (169.254.x) - 非HTTP(S)协议
This commit is contained in:
@@ -2,6 +2,50 @@ const vm = require('vm')
|
|||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const { isBalanceScriptEnabled } = require('../utils/featureFlags')
|
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){...} })
|
* - 脚本格式:({ request: {...}, extractor: function(response){...} })
|
||||||
@@ -55,6 +99,11 @@ class BalanceScriptService {
|
|||||||
throw new Error('脚本 request.url 不能为空')
|
throw new Error('脚本 request.url 不能为空')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSRF防护:验证URL安全性
|
||||||
|
if (!isUrlSafe(request.url)) {
|
||||||
|
throw new Error('脚本 request.url 不安全:禁止访问内网地址、localhost或使用非HTTP(S)协议')
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof extractor !== 'function') {
|
if (typeof extractor !== 'function') {
|
||||||
throw new Error('脚本 extractor 必须是函数')
|
throw new Error('脚本 extractor 必须是函数')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ const parseBooleanEnv = (value) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否允许执行“余额脚本”(安全开关)
|
* 是否允许执行"余额脚本"(安全开关)
|
||||||
* 默认开启,便于保持现有行为;如需禁用请显式设置 BALANCE_SCRIPT_ENABLED=false(环境变量优先)
|
* ⚠️ 安全警告:vm模块非安全沙箱,默认禁用。如需启用请显式设置 BALANCE_SCRIPT_ENABLED=true
|
||||||
|
* 仅在完全信任管理员且了解RCE风险时才启用此功能
|
||||||
*/
|
*/
|
||||||
const isBalanceScriptEnabled = () => {
|
const isBalanceScriptEnabled = () => {
|
||||||
if (
|
if (
|
||||||
@@ -36,7 +37,8 @@ const isBalanceScriptEnabled = () => {
|
|||||||
config?.features?.balanceScriptEnabled ??
|
config?.features?.balanceScriptEnabled ??
|
||||||
config?.security?.enableBalanceScript
|
config?.security?.enableBalanceScript
|
||||||
|
|
||||||
return typeof fromConfig === 'boolean' ? fromConfig : true
|
// 默认禁用,需显式启用
|
||||||
|
return typeof fromConfig === 'boolean' ? fromConfig : false
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
Reference in New Issue
Block a user