消除'eval`以防止它改变任何值

时间:2017-11-22 21:48:16

标签: javascript

这只是前端,而不是后端。我也承认这是一个坏主意。此时我只是好奇。

我有一张记录表。我希望用户能够输入JavaScript条件语句,然后将其应用于表以过滤记录。

例如,要过滤掉名称少于6个字符的记录,我可以输入:

record.name.length < 6

不使用外部库,我发现这样做的最简单方法是使用eval。但是,在使用eval时,我当然会介绍用户破解代码的风险(不是一个大问题,因为这只是前端,但仍然是用户体验问题)。

我想清理用户输入,以便它不能更改任何值。到目前为止,我认为我只需要做这两件事就可以使eval“安全”:

  • 将任何单个等号#{1}}变为双等或三等号
  • 删除或转义括号=

有了这两个项目,我还需要做些什么来阻止用户输入更改值吗?

1 个答案:

答案 0 :(得分:4)

eval更安全的一种方法是使用the Function constructor。据我所知,这个答案是完全安全的,但很可能有一些我不知道或已经忘记的警告,所以每个人都可以随时回答我的回答错。

Function构造函数允许您从其字符串和参数名称列表构造函数。例如,函数

function(x, y) {
    return x + y;
}

可以写成

new Function('x', 'y', 'return x + y;')

或只是

Function('x', 'y', 'return x + y;')

请注意,尽管函数体可以访问函数定义中声明的变量,但它无法从调用Function构造函数的本地作用域访问变量;在这方面,它比eval更安全。

例外是全局变量;函数体可以访问它们。也许您希望其中一些可以访问;对于他们中的许多人来说,你可能不会。但是,有一种方法:将全局变量的名称声明为函数的参数,然后调用函数用伪值覆盖它们。例如,请注意此表达式返回全局Object

(function() { return Object; })()

但是这个会返回'not Object'

(function(Object) { return Object; })('not Object')

因此,要创建一个无法访问任何全局变量的函数,您所要做的就是在javascript字符串上调用Function构造函数,并使用以所有全局变量命名的参数,然后调用函数对所有全局变量都有一些无害的值。

当然,有些变量(例如record)你确实希望javascript代码能够访问。 Function的参数名称参数也可以用于此。我假设您有一个名为myArguments的对象,其中包含它们,例如:

var myArguments = {
    record: record
};

(顺便说一句,不要将其称为arguments,因为它是一个保留字。)现在我们需要函数参数的名称列表。有两种:来自myArguments的参数和我们想要覆盖的全局变量。方便地,在客户端javascript中,所有全局变量都是单个对象window中的属性。我相信在没有原型属性的情况下使用自己的属性就足够了。

var myArgumentNames = Object.keys(myArguments);
var globalNames = Object.keys(window);
var allArgumentNames = myArgumentNames.concat(globalNames);

接下来我们想要参数的值:

var myArgumentValues = myArgumentNames.map(function(key) {
    return myArguments[key];
};

我们不需要为全局变量做值部分;如果我们不这样做,他们只会被设置为undefined。 (哦,不要做Object.keys(myArguments).map(...),因为数组出错的可能性很小,因为Object.keys没有做出任何规定保证其返回值的顺序。你必须使用相同的数组myArgumentNames。)然后调用Function构造函数。由于Function的参数很多,明确地列出它们是不切实际的,但我们可以使用函数上的apply方法来解决这个问题:

var myFn = Function.apply(null, allArgumentNames.concat([jsString]))

现在我们只使用apply方法生成的参数列表调用此函数。对于此部分,请注意jsString可能包含对this的引用;我们希望确保this不会帮助用户做恶意的事情。脚本中this的值是apply的第一个参数。实际上这并不完全正确 - 如果jsString没有使用严格模式,那么尝试将this设置为undefinednull将会失败,并且this将成为全球对象。你可以通过强制脚本进入严格模式(使用'"use strict";\n' + jsString),或者只是将this设置为空对象来解决这个问题。像这样:

myFn.apply({}, myArgumentValues)