Ограничение eval() в узком пространстве

У меня есть файл javascript, который читает другой файл, который может содержать фрагменты javascript, которые должны быть eval() - ed. Предполагается, что фрагменты script соответствуют строгому подмножеству 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 );
    }
}

Фактический код не должен выглядеть так: я открыт для всех и всех странных трюков с закрытием и т.д. Но я действительно хочу знать, возможно ли это.

Ответы

Ответ 1

Короткий ответ: Нет. Если он в глобальном масштабе, он доступен для чего угодно.

Длинный ответ: если вы eval() ненадежный код, который действительно хочет читать или вмешиваться в вашу среду исполнения, вы ввернуты. Но если вы являетесь владельцем и доверяете исполняемому коду, включая eval() ed, вы можете подделать его, переопределив контекст выполнения:

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() его (или передавайте его на новый Function() или используйте его любым другим способом, который ведет себя как eval()).

Ответ 2

Вы не можете ограничить объем eval

btw см. этот пост

Может быть какой-то другой способ добиться того, чего вы хотите достичь в великой схеме вещей, но вы не можете каким-либо образом ограничить сферу действия eval. Вы можете скрыть определенные переменные как псевдо-частные переменные в javascript, но я не думаю, что это то, что вы собираетесь делать.

Ответ 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 не изменяет определенную переменную и позволяет автоматически исключать ее из псевдонимов (например, Math в этом случае не изменяется).
  • Вы можете дать eval контекст, в котором нужно запускать, передавая значения аргументов, которые могут отличаться от окружающего контекста.
  • Вы можете зафиксировать изменения контекста, также возвращая аргументы функции из функции, чтобы вы могли исследовать их вне eval.

Обратите внимание, что использование eval является частным случаем, поскольку по своей природе оно эффективно не может быть завернуто в другую функцию (поэтому мы должны сделать eval(safeEval(...))).

Конечно, выполнение всей этой работы может замедлить ваш код, но есть, конечно, места, где удар не будет иметь значения. Надеюсь, это поможет кому-то. И если кто-нибудь создаст доказательство концепции, я бы хотел увидеть ссылку на него здесь; )

Ответ 4

Существует проект под названием Google Caja. Вы можете использовать "песочницу" стороннего javascript, используя Caja. https://developers.google.com/caja/

Ответ 5

Не используйте 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);

Ничего страшного, как вы можете видеть.

Ответ 6

Подобно обложению динамической функции script в блочном подходе 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 не поддерживается во всех браузерах.

Ответ 7

Не выполняйте код, которому вы не доверяете. Глобалы всегда будут доступны. Если вы доверяете коду, вы можете выполнить его с определенными переменными в его области следующим образом:

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

это эквивалентно:

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

Ответ 8

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