Возможна ли песочница JavaScript, запущенного в браузере? - PullRequest
132 голосов
/ 12 октября 2008

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

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

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

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

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

Ответы [ 13 ]

54 голосов
/ 12 октября 2008

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

32 голосов
/ 13 октября 2008

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

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

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

23 голосов
/ 22 июня 2009

Я создал библиотеку для песочницы с именем 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
      }
  });
7 голосов
/ 04 июля 2014

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

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

6 голосов
/ 18 сентября 2014

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

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

4 голосов
/ 12 октября 2008

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

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

3 голосов
/ 06 марта 2014

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

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

2 голосов
/ 11 мая 2016

Улучшенная версия кода изолированной программной среды @ 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).

0 голосов
/ 11 августа 2016

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

0 голосов
/ 20 мая 2009

Я работал над упрощенной 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 будут в конечном счете связаны с областью выполнения (), поэтому не будет конфликтующих имен или работы с существующим кодом.

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

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

...