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

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

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

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

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

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


person Walter Rumsby    schedule 12.10.2008    source источник


Ответы (15)


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

person Darius Bacon    schedule 12.10.2008
comment
Быстрый тест показывает, что Caja не может защитить браузер от атак типа while (1) {} --- ЦП просто зависает. Аналогично a=[]; while (1) { a=[a,a]; }. - person David Given; 04.04.2014
comment
Да, отказ в обслуживании выходит за рамки: code.google. ru / p / google-caja / issues / detail? id = 1406 - person Darius Bacon; 04.04.2014
comment
Проект будет прекращен Google 31 января 2021 года. Они рекомендуют людям перейти на библиотеку Closure (github.com/ google / closure-library) вместо этого. - person Chris Owens; 13.12.2020

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

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

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

person Simon Lieschke    schedule 12.10.2008
comment
На их сайте я не вижу возможности использовать ADsafe. Нет возможности скачать, нет ссылки на код, ничего. Как можно попробовать ADsafe? - person B T; 10.12.2015
comment
Кроме того, он предотвращает любой доступ к this, что совершенно неприемлемо. Вы не можете написать хороший javascript без использования this. - person B T; 10.12.2015
comment
В нижней части Страница структуры виджета ADsafe. - person Simon Lieschke; 08.01.2016
comment
@BT Я писал целые проекты без использования this. Избежать параметра с неудачным названием несложно. - person soundly_typed; 04.01.2017
comment
@mindeavour Значит, вы полностью отказались от объектно-ориентированного JavaScript. Я бы сказал, что это недопустимо. - person B T; 05.01.2017
comment
@BT Было бы глупо сказать, что завершение реальных проектов недопустимо. Но я сожалею, что начал это обсуждение, и должен уйти; здесь не место обсуждать такие вещи (извините). Я в твиттере, если вы хотите продолжить обсуждение. - person soundly_typed; 05.01.2017
comment
@mindeavor В реальных проектах используется объектно-ориентированное программирование и this. Пожалуйста, откажитесь, ваш товар - мусор. Не разрешить this означает, что действительный javascript не работает. Это рецепт быть занозой в заднице. Да, вы могли это сделать, но по определению гостевой код - это не вы, это кто-то другой - гость. И сказать им, что они не могут использовать какую-либо библиотеку, использующую this, означает, что они покажут вам большой средний палец и никогда не интегрируются с вашим дерьмом. - person B T; 05.01.2017
comment
@BT (я продолжу, поскольку это имеет отношение к вопросу) Каждый раз, когда вы запускаете код в чужой среде, вы сталкиваетесь с правилами и ограничениями. Я бы не назвал это неприемлемым. Может быть, заноза в заднице. Но не недопустимо. В конце концов, для каждого использования this существует равный, эквивалентный способ, отличный от this (в конце концов, это всего лишь параметр). - person soundly_typed; 05.01.2017
comment
как это использовать? я вообще не вижу никакой документации - person slier; 16.01.2017
comment
@slier Я отредактировал свой ответ, подробно указав, где вы можете увидеть пример использования ADsafe. - person Simon Lieschke; 16.01.2017

Я создал изолированную библиотеку под названием 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
      }
  });
person Eli Grey    schedule 21.06.2009
comment
+1: Это выглядит действительно круто. Насколько безопасно выполнять код пользователя таким образом? - person Konstantin Tarkus; 10.11.2010
comment
Очень безопасно. Ознакомьтесь с обновленной библиотекой на github. - person Eli Grey; 10.11.2010
comment
этот проект все еще поддерживается? Вижу, не обновлялся уже более 2-х лет ... - person Yanick Rochon; 29.10.2012
comment
Мне это нравится, за исключением того, что если вы хотите использовать песочницу, но по-прежнему разрешаете доступ коду, чтобы сказать jQuery, это не сработает, поскольку веб-воркеры не разрешают манипуляции с DOM. - person Rahly; 19.03.2015
comment
Привет, Эли! Спасибо за отличную библиотеку, планируете ли вы ее поддерживать? У меня есть запрос на изменение для добавления функций отладки, что должно быть возможно при быстром просмотре кода. Пожалуйста, дайте мне знать, что вы думаете? - person user1514042; 15.05.2015
comment
@Rahly: Все, что разрешено манипулировать DOM, по определению небезопасно. Как вы представляете себе доступ к DOM в песочнице? - person Sasha Chedygov; 15.06.2020
comment
В теории? Вы должны создать фрагмент документа, который заключен в тюрьму, где любые манипуляции / обходы ограничены деревом фрагментов. - person Rahly; 16.06.2020

Как упоминалось в других ответах, достаточно заблокировать код в изолированном iframe (без отправки его на серверную сторону) и обмениваться сообщениями.

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

http://asvd.github.io/jailed/demos/web/console/ < / а>

person asvd    schedule 18.09.2014

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

person MarcG    schedule 11.05.2016

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

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

person gronostaj    schedule 03.07.2014

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

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

person olliej    schedule 12.10.2008
comment
Вы можете объяснить, как это перерастает в проблему остановки? - person hdgarrood; 24.04.2013
comment
Теоретическая невозможность решения проблемы остановки действительно применима только к статическому анализу кода. Песочницы могут делать такие вещи, как ограничение времени, чтобы справиться с проблемой остановки. - person Aviendha; 29.11.2015

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

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

person David Fraser    schedule 11.08.2016

Уродливый способ, но, возможно, это сработает для вас:

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

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

person alejandro    schedule 06.03.2014
comment
Тривиально получить window обратно от этого. sandboxcode('console.log((0,eval)("this"))') - person Ry-♦; 03.06.2015
comment
Мне нужно выяснить, как предотвратить это - person alejandro; 05.07.2015
comment
@alejandro Вы нашли способ предотвратить это? - person Wilt; 14.12.2015
comment
Сломал себе голову, пока не понял, что вы можете делать eval = 0 глобально перед вызовом песочницы (сохраняя исходную функцию во временном режиме), и тогда оба глобальных window.eval и eval не будут доступны. Следующий взлом, пожалуйста! Потому что я действительно рассматриваю этот вариант. - person YoniXw; 28.12.2018
comment
Моя реализация просто добавляет: function sbx(s,p) {e = eval; eval = function(t){console.log("GOT GOOD")}; sandboxcode(s,p); eval =e} - person YoniXw; 28.12.2018
comment
@YoniXw: Надеюсь, ты ни для чего его не использовал. Ни один такой подход никогда не сработает. (_=>_).constructor('return this')() - person Ry-♦; 10.03.2019
comment
Нет хорошего способа сделать это, вы всегда можете сделать это, и у вас будет доступ с IIFE function sandboxcode(string, inject) { "use strict"; return (new Function(`const window = {}; var_this = this; const console = this.console; ${string}`)).call({console}); } sandboxcode('console.log(function(){ return this}())'); - person alejandro; 11.03.2019

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

person Bret Cameron    schedule 07.08.2019
comment
vm2 не поддерживает среду выполнения в браузере. Однако он должен работать, если вы ищете код песочницы в приложении nodejs. - person kevin.groat; 13.06.2020

С помощью NISP вы сможете выполнять оценку в изолированной среде.

Хотя написанное вами выражение - это не совсем код JavaScript, вместо этого вы напишете S-выражения. Он идеально подходит для простых DSL, не требующих обширного программирования.

person Kannan Ramamoorthy    schedule 20.05.2020

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

     var sCode = "alert(document)";
    

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

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

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

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

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

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

person Sergey Ilinsky    schedule 12.10.2008
comment
Разве нет множества других способов добраться до глобального объекта? Например, в функции, вызываемой с помощью func.apply (null), это будет объект окна. - person mbarkhau; 02.09.2011
comment
Первый пример не подводит, это очень неверный пример песочницы. - person Andy E; 23.04.2012
comment
var sCode = this.alert ('НЕИСПРАВНОСТЬ'); - person Leonard Pauli; 04.07.2013

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

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

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

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

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

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

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

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

person Dimitry    schedule 12.10.2008
comment
Если кто-то пытается сделать что-то вредоносное, простое регулярное выражение просто не может этого сделать - take (function () {this [loca +tion] = example.com;}) () Как правило, если вы не можете доверять своим пользователям (как в случае с любым сайтом, на котором произвольные люди могут добавлять контент), блокировка всех js-файлов необходима. - person olliej; 12.10.2008
comment
Раньше я использовал нечто подобное. Он не идеален, но большую часть пути он вам поможет. - person Sugendran; 12.10.2008
comment
olliej, вы правы насчет ограничений такой техники. Как насчет перезаписи глобальных переменных, таких как ‹code› var window = null, document = null, this = {}; ‹/code›? - person Dimitry; 12.10.2008
comment
Дмитрий З, перезапись этих переменных запрещена [в некоторых браузерах]. Также проверьте мое решение в списке ответов - оно работает. - person Sergey Ilinsky; 12.10.2008

Я работал над упрощенной песочницей JavaScript, позволяющей пользователям создавать апплеты для моего сайта. Хотя я все еще сталкиваюсь с некоторыми проблемами с предоставлением доступа к 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.

person Community    schedule 20.05.2009
comment
Это ничего не делает в песочнице. Улучшенный код может удалять элементы и таким образом переходить в глобальную область видимости или получать ссылку на глобальную область действия, выполняя (function () {return this;}) () - person Mike Samuel; 30.09.2009

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

(function (alert) {

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

}) ();

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


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

person John Millikin    schedule 12.10.2008
comment
Если пользователь введет window.alert вместо простого предупреждения, он обойдет это ограничение. - person Quentin; 12.10.2008
comment
@Dorward: да, следовательно, запрещенные объекты. wrunsby должен решить, к каким объектам пользователю не разрешен доступ, и поместить их в список параметров. - person John Millikin; 12.10.2008
comment
Есть только один объект - окно. Если не заблокировать к нему доступ, то через него доступно все. Если вы заблокируете его, скрипт не сможет получить доступ ни к одному из своих свойств (поскольку слово alert вместо window.alert просто подразумевает окно). - person Quentin; 12.10.2008
comment
@Doward: это не тот случай, когда вы заблокируете window.alert, но предупреждение все равно будет работать, попробуйте. Это потому, что окно также является глобальным объектом. Потребуется заблокировать окно и любое свойство или метод окна, к которому пользовательский код не должен получать доступ. - person AnthonyWJones; 12.10.2008