关于javascript:将eval()限制在一个狭窄的范围内

Restricting eval() to a narrow scope

我有一个javascript文件,读取另一个文件,其中可能包含需要eval() - ed的javascript片段。 脚本片段应该符合javascript的严格子集,这限制了它们可以做什么以及它们可以更改哪些变量,但我想知道是否有某种方法可以通过阻止eval在全局范围内查看变量来强制执行此操作。 类似于以下内容:

1
2
3
4
5
6
7
8
9
10
11
function safeEval( fragment )
{
    var localVariable = g_Variable;

    {
        // do magic scoping here so that the eval fragment can see localVariable
        // but not g_Variable or anything else outside function scope

        eval( fragment );
    }
}

实际的代码不需要看起来像这样 - 我对任何和所有带闭包的奇怪技巧都是开放的等等。但我确实想知道这是否可能。


简答:不。如果它在全球范围内,它可用于任何事物。

答案很长:如果你是eval()不信任的代码,真的想要阅读或搞乱你的执行环境,你就搞砸了。但是,如果您拥有并信任所有正在执行的代码,包括eval() ed,您可以通过覆盖执行上下文来伪造它:

1
2
3
4
5
6
7
8
9
10
11
12
function maskedEval(scr)
{
    // set up an object to serve as the context for the code
    // being evaluated.
    var mask = {};
    // mask global properties
    for (p in this)
        mask[p] = undefined;

    // execute script in private context
    (new Function("with(this) {" + scr +"}")).call(mask);
}

我必须再次强调:

This will only serve to shield trusted code from the context in which it is executed. If you don't trust the code, DO NOT eval() it (or pass it to new Function(), or use it in any other way that behaves like eval()).


Shog9?答案很棒。 但是如果你的代码只是一个表达式,代码将被执行,不会返回任何内容。 对于表达式,请使用

1
2
3
4
5
function evalInContext(context, js) {

  return eval('with(context) { ' + js + ' }');

}

以下是如何使用它:

1
2
3
var obj = {key: true};

evalInContext(obj, 'key ?"YES" :"NO"');

它将返回"YES"

如果您不确定要执行的代码是表达式还是语句,可以将它们组合在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function evalInContext(context, js) {

  var value;

  try {
    // for expressions
    value = eval('with(context) { ' + js + ' }');
  } catch (e) {
    if (e instanceof SyntaxError) {
      try {
        // for statements
        value = (new Function('with(this) { ' + js + ' }')).call(context);
      } catch (e) {}
    }
  }

  return value;
}


这是一个想法。如果您使用静态分析器(例如,您可以使用esprima构建的东西)来确定eval'd代码使用哪些外部变量,并将它们作为别名。"外部代码"是指eval'd代码使用但未声明的变量。这是一个例子:

1
2
3
4
eval(safeEval(
    "var x = window.theX;"
    +"y = Math.random();"
    +"eval('window.z = 500;');"))

其中safeEval返回使用阻止访问外部变量的上下文修改的javascript字符串:

1
2
3
4
5
";(function(y, Math, window) {"
  +"var x = window.theX;"
  +"y = Math.random();"
  +"eval(safeEval('window.z = 500;');"
"})();"

现在你可以做几件事:

  • 您可以确保eval'd代码无法读取外部变量的值,也不能写入它们(通过传递undefined作为函数参数,或者不传递参数)。或者你可以简单地在变量被不安全地访问的情况下抛出异常。
  • 您还要确保eval创建的变量不会影响周围的范围
  • 您可以通过在闭包之外声明这些变量而不是函数参数来允许eval在周围范围内创建变量
  • 您可以通过复制外部变量的值并将它们用作函数的参数来允许只读访问
  • 您可以通过告诉safeEval不为这些特定名称添加别名来允许对特定变量的读写访问
  • 您可以检测eval不修改特定变量并允许自动排除别名的情况(例如,在这种情况下,数学未被修改)
  • 您可以通过传入可能与周围上下文不同的参数值,为eval提供运行的上下文
  • 您还可以通过从函数返回函数参数来捕获上下文更改,以便您可以在eval之外检查它们。

请注意,eval的使用是一种特殊情况,因为从本质上讲,它实际上不能包含在另一个函数中(这就是为什么我们必须做eval(safeEval(...)))。

当然,完成所有这些工作可能会减慢你的代码速度,但肯定会有一些重要的地方。希望这有助于某人。如果有人创建概念证明,我很乐意在这里看到它的链接; )


与上面with块方法中的动态函数包装脚本类似,这允许您将伪全局变量添加到要执行的代码中。您可以通过将特定事物添加到上下文中来"隐藏"特定事物。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function evalInContext(source, context) {
    source = '(function(' + Object.keys(context).join(', ') + ') {' + source + '})';

    var compiled = eval(source);

    return compiled.apply(context, values());

    // you likely don't need this - use underscore, jQuery, etc
    function values() {
        var result = [];
        for (var property in context)
            if (context.hasOwnProperty(property))
                result.push(context[property]);
        return result;
    }
}

有关示例,请参见http://jsfiddle.net/PRh8t/。请注意,并非所有浏览器都支持Object.keys


你不能限制eval的范围

顺便看到这篇文章

可能还有其他方法可以完成你想要在宏观方案中完成的事情,但是你不能以任何方式限制eval的范围。你可能能够将某些变量隐藏为javascript中的伪私有变量,但我不认为这就是你想要的。


不要使用eval。还有一个替代的,js.js:用JS编写的JS解释器,这样你就可以在你设法设置的任何环境中运行JS程序。以下是项目页面中API的示例:

1
2
3
4
5
var jsObjs = JSJS.Init();
var rval = JSJS.EvaluateScript(jsObjs.cx, jsObjs.glob,"1 + 1");
var d = JSJS.ValueToNumber(jsObjs.cx, rval);
window.alert(d); // 2
JSJS.End(jsObjs);

你可以看到,没什么可怕的。


有一个名为Google Caja的项目。你可以使用Caja"沙盒"第三方javascript。 https://developers.google.com/caja/


不要执行你不信任的代码。 Globals始终可以访问。
如果您确实信任该代码,则可以使用其范围内的特定变量执行它,如下所示:

1
(new Function("a","b","alert(a + b);"))(1, 2);

这相当于:

1
2
3
(function (a, b) {
    alert(a + b);
})(1, 2);