35

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

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 );
    }
}

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

4

9 回答 9

57

简短的回答:不。如果它在全球范围内,它对任何东西都可用。

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

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);
}

再次,我必须强调:

这只会将受信任的代码从执行它的上下文中屏蔽掉。如果您不信任该代码,请不要使用eval()它(或将其传递给 new Function(),或以任何其他类似 的方式使用它eval())。

于 2009-02-12T22:39:15.140 回答
7

Shog9♦ 的回答很棒。但是,如果您的代码只是一个表达式,则代码将被执行并且不会返回任何内容。对于表达式,使用

function evalInContext(context, js) {

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

}

以下是如何使用它:

var obj = {key: true};

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

它会返回"YES"

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

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;
}
于 2017-04-09T13:01:10.223 回答
6

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

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并非所有浏览器都支持。

于 2014-05-25T05:27:06.250 回答
3

你不能限制 eval 的范围

顺便说一句,看到这个帖子

在宏伟的计划中,可能有其他方法可以完成您想要完成的事情,但您不能以任何方式限制 eval 的范围。您也许可以在 javascript 中将某些变量隐藏为伪私有变量,但我认为这不是您想要的。

于 2009-02-12T21:56:51.217 回答
3

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

于 2013-06-05T09:37:05.643 回答
3

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

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

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

";(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(...)))。

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

于 2013-08-11T20:44:16.037 回答
2

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

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);

如您所见,没什么可怕的。

于 2013-11-18T14:14:10.960 回答
2

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

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

这相当于:

(function (a, b) {
    alert(a + b);
})(1, 2);
于 2015-02-07T00:49:02.803 回答
2

我偶然发现我可以使用 Proxy 来限制范围对象,将变量屏蔽到范围之外似乎要容易得多。我不确定这种方法是否有缺点,但到目前为止它对我来说效果很好。

function maskedEval(src,ctx={})
{
    ctx = new Proxy(ctx,{
        has:()=>true
    })
    // execute script in private context
    let func = (new Function( "with(this) { " + src + "}"));
    func.call(ctx);
}
a = 1;
maskedEval("console.log(a)",{console:console});
maskedEval("console.log(a)",{console:console,a:22});

maskedEval("a = 1",{a:22})
console.log(a)
于 2021-04-15T04:54:56.887 回答