JS-Interpreter是一个有点着名的JavaScript解释器。它具有安全优势,因为它可以完全隔离您的代码document
,并允许您检测诸如无限循环和内存炸弹之类的攻击。这允许您安全地运行外部定义的代码。
我有一个对象,比如说o
:
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
我希望能够通过JS-Interpreter在process
中运行代码:
for (let i = 0; i < o.process.length; i++)
interpretWithinContext(o, o.process[i]);
interpretWithinContext
将使用第一个参数作为上下文创建解释器,即o
变为this
,第二个参数是要运行的代码行。运行上面的代码后,我希望o
为:
{
hidden: false,
regex: /^[a-z]+$/i,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: '^[a-z]+$'
}
即,现在设置了hidden
和regex
。
有人知道JS-Interpreter中是否可以这样做吗?
答案 0 :(得分:3)
我现在花了一些时间搞乱JS-Interpreter,试图找出from the source如何将对象放入解释器的范围,可以读取和修改。
不幸的是,这个库的构建方式,所有有用的内部事物都被缩小了,所以我们无法真正利用内部事物而只是将一个对象放入其中。尝试添加proxy object也失败了,因为该对象并未以“正常”方式使用。
所以我最初的方法是回到提供简单的实用程序函数来访问外部对象。这完全得到了库的支持,可能是最安全的与它交互的方式。它确实需要您更改process
代码,以便使用这些功能。但作为一种好处,它确实提供了一个非常干净的界面来与“外部世界”进行通信。您可以在以下隐藏代码段中找到解决方案:
function createInterpreter (dataObj) {
function initialize (intp, scope) {
intp.setProperty(scope, 'get', intp.createNativeFunction(function (prop) {
return intp.nativeToPseudo(dataObj[prop]);
}), intp.READONLY_DESCRIPTOR);
intp.setProperty(scope, 'set', intp.createNativeFunction(function (prop, value) {
dataObj[prop] = intp.pseudoToNative(value);
}), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"set('hidden', !get('visible'));",
"set('regex', new RegExp(get('validate'), 'i'));"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
&#13;
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
&#13;
然而,在发布上述解决方案后,我只是无法停止思考这个问题,所以我深入挖掘。据我所知,方法getProperty
和setProperty
不仅用于设置初始沙箱范围,还用于解释代码。因此,我们可以使用它为我们的对象创建类似代理的行为。
我的解决方案基于我发现的in an issue comment代码,通过修改Interpreter
类型来实现此目的。不幸的是,代码是用CoffeeScript编写的,也是基于一些旧版本的,所以我们不能完全按原样使用它。还有一个内部被缩小的问题,我们将在稍后讨论。
总体思路是将一个“连接对象”引入范围,我们将在getProperty
和setProperty
内作为特殊情况处理,以映射到我们的实际对象。
但是为此,我们需要覆盖这两个方法,因为它们被缩小并接收到不同的内部名称。幸运的是,源代码的结尾包含以下内容:
// Preserve top-level API functions from being pruned/renamed by JS compilers.
// …
Interpreter.prototype['getProperty'] = Interpreter.prototype.getProperty;
Interpreter.prototype['setProperty'] = Interpreter.prototype.setProperty;
因此,即使缩小器在右侧破坏了名称,也不会触及左侧的名称。这就是作者如何使特定功能可供公众使用。但是我们想覆盖它们,所以我们不能只覆盖友好的名字,我们还需要更换缩小的副本!但由于我们有办法访问这些函数,我们还可以搜索带有错位名称的任何其他副本。
这就是我在patchInterpreter
开始时在我的解决方案中所做的事情:定义我们将覆盖现有方法的新方法。然后,查找引用这些函数的所有名称(已损坏或未损坏),并用新定义替换它们。
最后,在修补Interpreter
之后,我们只需要在范围中添加一个连接对象。我们不能使用名称this
,因为已经使用过,但我们可以选择其他内容,例如o
:
function patchInterpreter (Interpreter) {
const originalGetProperty = Interpreter.prototype.getProperty;
const originalSetProperty = Interpreter.prototype.setProperty;
function newGetProperty(obj, name) {
if (obj == null || !obj._connected) {
return originalGetProperty.call(this, obj, name);
}
const value = obj._connected[name];
if (typeof value === 'object') {
// if the value is an object itself, create another connected object
return this.createConnectedObject(value);
}
return value;
}
function newSetProperty(obj, name, value, opt_descriptor) {
if (obj == null || !obj._connected) {
return originalSetProperty.call(this, obj, name, value, opt_descriptor);
}
obj._connected[name] = this.pseudoToNative(value);
}
let getKeys = [];
let setKeys = [];
for (const key of Object.keys(Interpreter.prototype)) {
if (Interpreter.prototype[key] === originalGetProperty) {
getKeys.push(key);
}
if (Interpreter.prototype[key] === originalSetProperty) {
setKeys.push(key);
}
}
for (const key of getKeys) {
Interpreter.prototype[key] = newGetProperty;
}
for (const key of setKeys) {
Interpreter.prototype[key] = newSetProperty;
}
Interpreter.prototype.createConnectedObject = function (obj) {
const connectedObject = this.createObject(this.OBJECT);
connectedObject._connected = obj;
return connectedObject;
};
}
patchInterpreter(Interpreter);
// actual application code
function createInterpreter (dataObj) {
function initialize (intp, scope) {
// add a connected object for `dataObj`
intp.setProperty(scope, 'o', intp.createConnectedObject(dataObj), intp.READONLY_DESCRIPTOR);
}
return function (code) {
const interpreter = new Interpreter(code, initialize);
interpreter.run();
return interpreter.value;
};
}
let o = {
hidden: null,
regex: null,
process: [
"o.hidden = !o.visible;",
"o.regex = new RegExp(o.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
const interprete = createInterpreter(o);
for (const process of o.process) {
interprete(process);
}
console.log(o.hidden); // false
console.log(o.regex); // /^[a-z]+$/i
&#13;
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
&#13;
就是这样!请注意,虽然该新实现已经可以使用嵌套对象,但它可能不适用于每种类型。因此,您应该小心将哪些对象传递到沙箱中。使用基本或原始类型创建单独且显式安全的对象可能是个好主意。
答案 1 :(得分:1)
尚未尝试JS-Interpreter
。您可以使用new Function()
和Function.prototype.call()
来达到要求
let o = {
hidden: null,
regex: null,
process: [
"this.hidden = !this.visible;",
"this.regex = new RegExp(this.validate, 'i');"
],
visible: true,
validate: "^[a-z]+$"
};
for (let i = 0; i < o.process.length; i++)
console.log(new Function(`return ${o.process[i]}`).call(o));
&#13;
答案 2 :(得分:0)
你好,可能会解释看起来像这样的东西吗?
let interpretWithinContext = (function(o, p){
//in dunno for what you use p because all is on object o
o.hidden = (o.hidden === null) ? false : o.hidden;
o.regex = (o.regex === null) ? '/^[a-z]+$/i' : o.regex;
console.log(o);
return o;
});