JS-Interpreter - 改变“this”上下文
JS-Interpreter - changing “this” context
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
,第二个参数是 [=36= 的代码行].在 运行 上面的代码之后,我希望 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 中是否可行?
没试过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));
您好,interpretWithinContext 看起来像那样吗?
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;
});
我现在花了一段时间研究 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
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
然而,在发布上述解决方案之后,我无法停止思考这个问题,所以我进行了更深入的挖掘。据我了解,方法 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;
所以即使缩小器破坏了右边的名字,它也不会触及左边的名字。这就是作者如何让特定功能可供 public 使用。但是我们要覆盖他们,所以我们不能只覆盖友好的名字,我们还需要替换缩小的副本!但是因为我们有办法访问这些函数,我们也可以搜索它们的任何其他具有损坏名称的副本。
这就是我在 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
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
就是这样!请注意,虽然新的实现已经适用于嵌套对象,但它可能不适用于每种类型。因此,您可能应该小心将哪种对象传递到沙箱中。仅使用基本或原始类型创建单独且明确安全的对象可能是个好主意。
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
,第二个参数是 [=36= 的代码行].在 运行 上面的代码之后,我希望 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 中是否可行?
没试过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));
您好,interpretWithinContext 看起来像那样吗?
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;
});
我现在花了一段时间研究 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
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
然而,在发布上述解决方案之后,我无法停止思考这个问题,所以我进行了更深入的挖掘。据我了解,方法 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;
所以即使缩小器破坏了右边的名字,它也不会触及左边的名字。这就是作者如何让特定功能可供 public 使用。但是我们要覆盖他们,所以我们不能只覆盖友好的名字,我们还需要替换缩小的副本!但是因为我们有办法访问这些函数,我们也可以搜索它们的任何其他具有损坏名称的副本。
这就是我在 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
<script src="https://neil.fraser.name/software/JS-Interpreter/acorn_interpreter.js"></script>
就是这样!请注意,虽然新的实现已经适用于嵌套对象,但它可能不适用于每种类型。因此,您可能应该小心将哪种对象传递到沙箱中。仅使用基本或原始类型创建单独且明确安全的对象可能是个好主意。