Можно ли изолировать JavaScript, работающий в браузере?

145

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

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

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

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

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

Уолтер Рамсби
источник

Ответы:

54

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

Дариус Бэкон
источник
5
Быстрый тест показывает, что Caja не может защитить браузер от атак типа CPU, например, while (1) {}он просто зависает. Точно так же a=[]; while (1) { a=[a,a]; }.
Дэвид Гивен
5
Да, отказ в обслуживании выходит за рамки: code.google.com/p/google-caja/issues/detail?id=1406
Дариус Бэкон,
32

Взгляните на ADsafe Дугласа Крокфорда :

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

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

Саймон Лишке
источник
На их сайте я не вижу возможности использовать ADsafe. Нет возможности скачать, нет ссылки на код, ничего. Как можно попробовать ADsafe?
BT
2
Кроме того, он предотвращает любой доступ к ним this, что совершенно недопустимо. Вы не можете написать хороший javascript без использования this.
BT
7
@BT Я писал целые проекты без использования this. Избежать плохо названного параметра несложно.
soundly_typed
4
@BT Было бы глупо сказать, что завершение реальных проектов недопустимо. Но я сожалею о начале этого обсуждения и должен уйти; здесь не место обсуждать такие вещи (извините). Я в твиттере, если вы хотите продолжить обсуждение.
soundly_typed
2
@BT (я продолжу, поскольку это имеет отношение к вопросу) Каждый раз, когда вы запускаете код в чужой среде, вы сталкиваетесь с правилами и ограничениями. Я бы не назвал это неприемлемым. Может быть, «заноза в заднице». Но не недопустимо. В конце концов, для каждого использования thisсуществует равный, эквивалентный thisспособ сделать это (в конце концов, это всего лишь параметр).
soundly_typed
23

Я создал библиотеку песочницы под названием 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
      }
  });
Эли Грей
источник
+1: Это выглядит действительно круто. Насколько безопасно выполнять код пользователя таким образом?
Константин Таркус
1
Очень безопасно. Проверьте обновленную библиотеку на github .
Эли Грей
1
этот проект все еще поддерживается? Я вижу, что он не обновлялся более двух лет ...
Яник Рочон
Мне это нравится, за исключением того, что если вы хотите использовать песочницу, но все же разрешаете доступ коду, чтобы сказать jQuery, это не сработает, поскольку веб-воркеры не разрешают манипуляции с DOM.
Rahly
1
Привет, Эли! Спасибо за отличную библиотеку, планируете ли вы ее поддерживать? У меня есть запрос на изменение для добавления функций отладки, что должно быть возможно при быстром просмотре кода. Пожалуйста, дай мне знать, что ты думаешь?
user1514042
9

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

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

asvd
источник
8

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

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

гроностай
источник
5

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

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

оллией
источник
2
Вы можете объяснить, как это перерастает в проблему остановки?
hdgarrood
2
Теоретическая невозможность решения проблемы остановки действительно применима только к статическому анализу кода. Песочницы могут выполнять такие действия, как ограничение времени для решения проблемы остановки.
Aviendha
5

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

MarcG
источник
2

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

Дэвид Фрейзер
источник
1

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

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

Алехандро
источник
3
Тривиально вернуться windowк этому. sandboxcode('console.log((0,eval)("this"))')
Ry-
Я должен придумать, как это предотвратить
Алехандро
@alejandro Вы нашли способ предотвратить это?
Уилт
1
Моя реализация просто добавляет:function sbx(s,p) {e = eval; eval = function(t){console.log("GOT GOOD")}; sandboxcode(s,p); eval =e}
YoniXw
2
@YoniXw: Надеюсь, вы не использовали его ни для чего. Ни один такой подход никогда не сработает. (_=>_).constructor('return this')()
Ry-
0

С NISP вы сможете проводить оценку в песочнице. Хотя написанное вами выражение - это не совсем JS, вместо этого вы напишете s-выражения. Идеально подходит для простых DSL, не требующих обширного программирования.

Каннан Рамамурти
источник
0

По состоянию на 2019 год , VM2 выглядит как самый популярный и наиболее регулярно обновляемом решение работает JavaScript в Node.js . Я не знаю интерфейсного решения.

Брет Кэмерон
источник
vm2 не поддерживает среду выполнения в браузере. Однако он должен работать, если вы ищете код песочницы в приложении nodejs.
kevin.groat
-3

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

var sCode = "alert(document)";

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

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

Эти две строки при выполнении завершатся ошибкой, потому что функция «оповещение» недоступна из «песочницы».

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

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

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

Сергей Ильинский
источник
7
Нет ли множества других способов добраться до глобального объекта? Например, в функции, вызываемой с помощью func.apply (null), «this» будет объектом окна.
mbarkhau 02
5
Первый пример не подводит, это очень неверный пример песочницы.
Andy E
1
var sCode = "this.alert ('FAIL')";
Леонард Паули
-4

Откуда взялся этот пользовательский JavaScript?

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

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

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

 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  ;

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

Димитрий
источник
2
Если кто-то пытается сделать что-то вредоносное, простое регулярное выражение просто не может этого сделать - используйте (function () {this ["loca" + "tion "] =" example.com ";}) () В общем, если вы не могут доверять вашим пользователям (как в случае с любым сайтом, на котором произвольные люди могут добавлять контент), блокировка всех js необходима.
olliej
Раньше я использовал нечто подобное. Он не идеален, но большую часть пути он вам поможет.
Sugendran
olliej, вы правы насчет ограничений такой техники. Как насчет перезаписи глобальных переменных, таких как <code> var window = null, document = null, this = {}; </code>?
Димитрий
Дмитрий З, перезапись этих переменных запрещена [в некоторых браузерах]. Также проверьте мое решение в списке ответов - оно работает.
Сергей Ильинский,
-5

Я работал над упрощенной песочницей 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);
            }
        }
    };
}

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

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

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


источник
8
Это не песочница. Расширенный код может удалять элементы и таким образом переходить в глобальную область видимости или получать ссылку на глобальную область действия, выполнив (function () {return this;}) ()
Майк Сэмюэл
-6

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

(function (alert) {

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

}) ();

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


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

Джон Милликин
источник
2
Если пользователь ввел window.alert вместо обычного предупреждения, он обойдет это ограничение.
Квентин,
@Dorward: да, отсюда и «запрещенные объекты». wrunsby должен решить, к каким объектам пользователю не разрешен доступ, и поместить их в список параметров.
Джон Милликин,
Есть только один объект - окно. Если не заблокировать к нему доступ, то через него доступно все. Если вы заблокируете его, скрипт не сможет получить доступ ни к одному из своих свойств (поскольку слово alert вместо window.alert просто подразумевает окно).
Квентин,
@Doward: это не тот случай, когда вы заблокируете window.alert, но предупреждение все равно будет работать, попробуйте. Это потому, что окно также является глобальным объектом. Потребуется заблокировать окно и любое свойство или метод окна, к которому пользовательский код не должен получать доступ.
AnthonyWJones,