Возможно ли работать с песочницей JavaScript в браузере?

Мне интересно, возможно ли использовать JavaScript-песочницу в браузере для предотвращения доступа к функциям, которые обычно доступны для кода JavaScript, запущенного на странице HTML.

Например, скажем, я хочу предоставить JavaScript API для конечных пользователей, чтобы позволить им определять обработчики событий, которые будут выполняться, когда происходят "интересные события", но я не хочу, чтобы эти пользователи обращались к свойствам и функциям window объект. Могу ли я это сделать?

В простейшем случае, допустим, я хочу запретить пользователям, вызывающим alert. Несколько подходов, о которых я могу думать, следующие:

  • Переопределить window.alert глобально. Я не думаю, что это был бы действительный подход, потому что другой код, запущенный на странице (т.е. Материал, не созданный пользователями в обработчиках событий), может захотеть использовать alert.
  • Отправьте код обработчика событий на сервер для обработки. Я не уверен, что отправка кода на сервер для обработки - это правильный подход, потому что обработчики событий должны запускаться в контексте страницы.

Возможно, решение, в котором сервер обрабатывает определенную пользователем функцию, а затем генерирует обратный вызов, который будет выполняться на клиенте, будет работать? Даже если этот подход работает, есть ли лучшие способы решения этой проблемы?

Ответ 1

Google Caja - это переводчик исходного кода, который "позволяет вам размещать ненадежные сторонние HTML и JavaScript встроенные в вашу страницу и при этом оставаться в безопасности".

Ответ 2

Посмотрите Дуглас Крокфорд ADsafe:

ADsafe позволяет безопасно размещать гостевой код (например, рекламу или виджеты сторонних разработчиков) на любой веб-странице. ADsafe определяет подмножество JavaScript, достаточно мощное, чтобы позволить гостевому коду выполнять ценные взаимодействия и в то же время предотвращать вредоносные или случайные повреждения или вторжения. Подмножество ADsafe может быть проверено механически с помощью таких инструментов, как JSLint, чтобы никакой инспекции человека не требовалось для проверки кода гостя для безопасности. Подмножество ADsafe также обеспечивает хорошие методы кодирования, увеличивая вероятность того, что гостевой код будет работать правильно.

Вы можете увидеть пример использования ADsafe, просмотрев файлы template.html и template.js в репозитарии проекта GitHub.

Ответ 3

Я создал библиотеку песочницы, называемую jsandbox, которая использует веб-работников для оценки кода в песочнице. Он также имеет метод ввода для явного предоставления изолированных кодовых данных, которые он иначе не смог бы получить.

Ниже приведен пример API:

jsandbox
    .eval({
      code    : "x=1;Math.round(Math.pow(input, ++x))",
      input   : 36.565010597564445,
      callback: function(n) {
          console.log("number: ", n); // number: 1337
      }
  }).eval({
      code   : "][];.]\\ (*# ($(! ~",
      onerror: function(ex) {
          console.log("syntax error: ", ex); // syntax error: [error object]
      }
  }).eval({
      code    : '"foo"+input',
      input   : "bar",
      callback: function(str) {
          console.log("string: ", str); // string: foobar
      }
  }).eval({
      code    : "({q:1, w:2})",
      callback: function(obj) {
          console.log("object: ", obj); // object: object q=1 w=2
      }
  }).eval({
      code    : "[1, 2, 3].concat(input)",
      input   : [4, 5, 6],
      callback: function(arr) {
          console.log("array: ", arr); // array: [1, 2, 3, 4, 5, 6]
      }
  }).eval({
      code    : "function x(z){this.y=z;};new x(input)",
      input   : 4,
      callback: function(x) {
          console.log("new x: ", x); // new x: object y=4
      }
  });

Ответ 4

РЕДАКТИРОВАТЬ. Хотя я не знаю, как избежать белого списка ниже, я бы запустил рабочего из изолированной <iframe> тоже, на всякий случай, и будет второй @gronostajs js.js если проблема исчерпания памяти является проблемой.

Веб-рабочие предоставляют удобный способ создания другого контекста script, который затем может быть агрессивно изолирован, не затрагивая родителя.

Реализация с использованием promises:

function safeEval(untrustedCode) {
    return new Promise(function (resolve, reject) {
        var worker = new Worker('eval.js');

        worker.onmessage = function (e) {
            worker.terminate();
            resolve(e.data);
        };

        worker.onerror = function (e) {
            reject(new Error(e.message));
        };

        worker.postMessage(untrustedCode);

        setTimeout(function () {
            worker.terminate();
            reject(new Error('The worker timed out.'));
        }, 1000);
    });
}

eval.js (вероятно, вы захотите расширить белый список):

(function (global) {
    'use strict';

    var _postMessage = postMessage;
    var _addEventListener = addEventListener;

    (function () {
        var current = global;
        var keepProperties = [
            // required
            'Object', 'Function', 'Infinity', 'NaN', 'undefined',
            // optional, but trivial to get back
            'Array', 'Boolean', 'Number', 'String', 'Symbol',
            // optional
            'Map', 'Math', 'Set',
        ];

        do {
            Object.getOwnPropertyNames(current).forEach(function (name) {
                if (keepProperties.indexOf(name) === -1) {
                    delete current[name];
                }
            });

            current = Object.getPrototypeOf(current);
        } while (current !== Object.prototype);
    })();

    _addEventListener('message', function (e) {
        var f = new Function('', 'return (' + e.data + '\n);');
        _postMessage(f());
    });
})(this);

Отсутствие доступа к пользовательскому интерфейсу от работников, может быть завершено отдельно, не может связать пользовательский интерфейс или работающий script и является стандартным.

Ответ 5

Я думаю, что js.js стоит упомянуть здесь. Это JavaScript-интерпретатор, написанный на JavaScript.

Он примерно в 200 раз медленнее, чем родной JS, но его природа делает его идеальной средой песочницы. Другим недостатком является его размер - почти 600 кб, что может быть приемлемо для настольных компьютеров в некоторых случаях, но не для мобильных устройств.

Ответ 6

Как упоминалось в других ответах, этого достаточно, чтобы заключить код в изолированную среду iframe (без отправки его на серверную часть) и обмениваться сообщениями. Я бы посоветовал взглянуть на небольшую библиотеку, которую я создал в основном из-за необходимости предоставления некоторого API для ненадежного кода, как описано в вопросе: есть возможность экспортировать определенный набор функционирует прямо в песочнице, где работает недоверенный код. И еще есть демо, которое выполняет код, представленный пользователем в песочнице:

http://asvd.github.io/jailed/demos/web/console/

Ответ 7

Улучшенная версия кода песочницы веб-мастеров @RyanOHara, в одном файле (не требуется дополнительный файл eval.js).

function safeEval(untrustedCode)
    {
    return new Promise(function (resolve, reject)
    {

    var blobURL = URL.createObjectURL(new Blob([
        "(",
        function ()
            {
            var _postMessage = postMessage;
            var _addEventListener = addEventListener;

            (function (obj)
                {
                "use strict";

                var current = obj;
                var keepProperties = [
                    // required
                    'Object', 'Function', 'Infinity', 'NaN', 'undefined', 'caches', 'TEMPORARY', 'PERSISTENT', 
                    // optional, but trivial to get back
                    'Array', 'Boolean', 'Number', 'String', 'Symbol',
                    // optional
                    'Map', 'Math', 'Set',
                ];

                do {
                    Object.getOwnPropertyNames(current).forEach(function (name) {
                        if (keepProperties.indexOf(name) === -1) {
                            delete current[name];
                        }
                    });

                    current = Object.getPrototypeOf(current);
                }
                while (current !== Object.prototype);
                })(this);

            _addEventListener("message", function (e)
            {
            var f = new Function("", "return (" + e.data + "\n);");
            _postMessage(f());
            });
            }.toString(),
        ")()"], {type: "application/javascript"}));

    var worker = new Worker(blobURL);

    URL.revokeObjectURL(blobURL);

    worker.onmessage = function (evt)
        {
        worker.terminate();
        resolve(evt.data);
        };

    worker.onerror = function (evt)
        {
        reject(new Error(evt.message));
        };

    worker.postMessage(untrustedCode);

    setTimeout(function () {
        worker.terminate();
        reject(new Error('The worker timed out.'));
        }, 1000);
    });
    }

Проверьте это:

https://jsfiddle.net/kp0cq6yw/

var promise = safeEval("1+2+3");

promise.then(function (result) {
      alert(result);
      });

Он должен вывести 6 (протестирован в Chrome и Firefox).

Ответ 8

Все поставщики браузеров и спецификация HTML5 работают над фактическим значением изолированной среды, чтобы разрешать изолированные iframe, но все равно ограничиваются гранулярностью iframe.

В общем, никакая степень регулярных выражений и т.д. не может безопасно дезинформировать произвольный пользовательский JavaScript, поскольку он вырождается до проблемы с остановкой: -/

Ответ 9

Уродливо, но, возможно, это работает для вас, я взял все глобальные переменные и переопределил их в области песочницы, а также добавил строгий режим, чтобы они не могли получить глобальный объект, используя анонимную функцию.

function construct(constructor, args) {
  function F() {
      return constructor.apply(this, args);
  }
  F.prototype = constructor.prototype;
  return new F();
}
// Sanboxer 
function sandboxcode(string, inject) {
  "use strict";
  var globals = [];
  for (var i in window) {
    // <--REMOVE THIS CONDITION
    if (i != "console")
    // REMOVE THIS CONDITION -->
    globals.push(i);
  }
  globals.push('"use strict";\n'+string);
  return construct(Function, globals).apply(inject ? inject : {});
}
sandboxcode('console.log( this, window, top , self, parent, this["jQuery"], (function(){return this;}()));'); 
// => Object {} undefined undefined undefined undefined undefined undefined 
console.log("return of this", sandboxcode('return this;', {window:"sanboxed code"})); 
// => Object {window: "sanboxed code"}

https://gist.github.com/alejandrolechuga/9381781

Ответ 10

Независимый Javascript-интерпретатор с большей вероятностью даст надежную песочницу, чем встроенную версию встроенного браузера. Райан уже сказал js.js, но более современный проект JS-Interpreter. docs описывают, как выставлять различные функции интерпретатору, но его область действия в этом случае очень ограничена.

Ответ 11

По состоянию на 2019 год vm2 выглядит как наиболее популярное и регулярно обновляемое решение этой проблемы.

Ответ 12

1) Предположим, что у вас есть код для выполнения:

var sCode = "alert(document)";

Теперь предположим, что вы хотите выполнить его в песочнице:

new Function("window", "with(window){" + sCode + "}")({});

Эти две строки при выполнении не будут выполнены, поскольку функция "alert" недоступна из "песочницы"

2) И теперь вы хотите открыть член окна с вашей функциональностью:

new Function("window", "with(window){" + sCode + "}")({
    'alert':function(sString){document.title = sString}
});

В самом деле, вы можете добавить кавычки экранирования и сделать другую полировку, но я думаю, идея понятна.

Ответ 13

Где находится этот пользовательский JavaScript?

Существует не так много, что вы можете сделать о встраивании кода пользователя на свою страницу, а затем вызвать его из своего браузера (см. Greasemonkey, http://www.greasespot.net/). Это просто что-то браузеры.

Однако, если вы храните script в базе данных, затем извлекаете его и eval(), затем вы можете очистить script до его запуска.

Примеры кода, который удаляет все окна. и документ. ссылки:

 eval(
  unsafeUserScript
    .replace(/\/\/.+\n|\/\*.*\*\/, '') // Clear all comments
    .replace(/\s(window|document)\s*[\;\)\.]/, '') // removes window. or window; or window)
 )

Это пытается предотвратить выполнение (не проверено):

window.location = 'http://mydomain.com';
var w = window  ;

Существует множество ограничений, которые вы должны были бы применить к небезопасному пользователю script. К сожалению, для JavaScript недоступен "контейнер для песочницы".

Ответ 14

Я работал над простой js-песочницей, позволяя пользователям создавать апплеты для моего сайта. Хотя я все еще сталкиваюсь с некоторыми проблемами с предоставлением доступа DOM (parentNode просто не позволит мне сохранять вещи в безопасности =/), мой подход состоял в том, чтобы переопределить объект окна с некоторыми из его полезных/безвредных членов, а затем eval() пользователя код с этим переопределенным окном как область по умолчанию.

Мой "основной" код выглядит следующим образом... (я не показываю его полностью;)

function Sandbox(parent){

    this.scope = {
        window: {
            alert: function(str){
                alert("Overriden Alert: " + str);
            },
            prompt: function(message, defaultValue){
                return prompt("Overriden Prompt:" + message, defaultValue);
            },
            document: null,
            .
            .
            .
            .
        }
    };

    this.execute = function(codestring){

        // here some code sanitizing, please

        with (this.scope) {
            with (window) {
                eval(codestring);
            }
        }
    };
}

Итак, я могу указать Sandbox и использовать его execute() для запуска кода. Кроме того, все новые объявленные переменные в коде eval'd в конечном итоге будут связаны с областью execute(), поэтому не будет встречаться с именами или возиться с существующим кодом.

Хотя глобальные объекты все равно будут доступны, те, которые должны оставаться неизвестными для изолированного кода, должны быть определены как прокси в объекте Sandbox:: scope.

Надеюсь, это сработает для вас.

Ответ 15

Вы можете обернуть код пользователя в функцию, которая переопределяет запрещенные объекты в качестве параметров - тогда они будут undefined при вызове:

(function (alert) {

alert ("uh oh!"); // User code

}) ();

Разумеется, умные злоумышленники могут обойти это, проверив DOM Javascript и найдя неперекрываемый объект, содержащий ссылку на окно.


Другая идея - сканирование кода пользователя с помощью инструмента, такого как jslint. Убедитесь, что у него нет предустановленных переменных (или: только требуемые переменные), а затем, если какие-либо глобальные блоки установлены или доступны, не допускайте использования пользователя script. Опять же, может быть уязвимым для ходьбы по объектам DOM, которые пользователь может построить с использованием литералов, может иметь неявные ссылки на объект окна, к которым можно получить доступ, чтобы избежать песочницы.