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

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

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

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

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

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

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
152
0
51 409
15

Ответы 15

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

(function (alert) {

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

}) ();

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


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

Если пользователь введет window.alert вместо простого предупреждения, он обойдет это ограничение.

Quentin 12.10.2008 10:41

@Dorward: да, отсюда и «запрещенные объекты». wrunsby должен решить, к каким объектам пользователю не разрешен доступ, и поместить их в список параметров.

John Millikin 12.10.2008 10:47

Есть только один объект - окно. Если не заблокировать к нему доступ, то через него доступно все. Если вы заблокируете его, скрипт не сможет получить доступ ни к одному из его свойств (поскольку слово alert вместо window.alert просто подразумевает окно).

Quentin 12.10.2008 11:40

@Doward: это не тот случай, когда вы заблокируете window.alert, но предупреждение все равно будет работать, попробуйте. Это потому, что окно также является глобальным объектом. Потребуется заблокировать окно и любое свойство или метод окна, к которому пользовательский код не должен получать доступ.

AnthonyWJones 12.10.2008 16:10

Откуда взялся этот пользовательский 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 не существует «контейнера песочницы».

Если кто-то пытается сделать что-то вредоносное, простое регулярное выражение просто не может этого сделать - take (function () {this ["loca" + "tion "] =" example.com ";}) () В общем, если вы можете ' • доверяйте своим пользователям (как в случае с любым сайтом, на который произвольные люди могут добавлять контент), блокировка всех js необходима.

olliej 12.10.2008 11:47

Я использовал нечто подобное в прошлом. Это не идеально, но поможет вам в этом.

Sugendran 12.10.2008 13:34

olliej, вы правы насчет ограничений такой техники. Как насчет перезаписи глобальных переменных, таких как <code> var window = null, document = null, this = {}; </code>?

Dimitry 12.10.2008 20:18

Дмитрий З, перезапись этих переменных запрещена [в некоторых браузерах]. Также проверьте мое решение в списке ответов - оно работает.

Sergey Ilinsky 12.10.2008 23:00

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

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

Вы можете объяснить, как это перерастает в проблему остановки?

hdgarrood 24.04.2013 04:03

Теоретическая невозможность решения проблемы остановки действительно применима только к статическому анализу кода. Песочницы могут делать такие вещи, как ограничение времени, чтобы справиться с проблемой остановки.

Aviendha 29.11.2015 22:44

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

Быстрый тест показывает, что Caja не может защитить браузер от атак ЦП, таких как while (1) {}, - он просто зависает. Аналогично a=[]; while (1) { a=[a,a]; }.

David Given 04.04.2014 02:45

Да, отказ в обслуживании выходит за рамки: code.google.com/p/google-caja/issues/detail?id=1406

Darius Bacon 04.04.2014 23:53

Проект будет прекращен Google 31 января 2021 года. Вместо этого они рекомендуют людям перейти в библиотеку Closure (github.com/google/closure-library).

Chris 13.12.2020 09:37

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

var sCode = "alert(document)";

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

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

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

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

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

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

Разве нет множества других способов добраться до глобального объекта? Например, в функции, вызываемой с помощью func.apply (null), «this» будет объектом окна.

mbarkhau 02.09.2011 16:09

Первый пример не подводит, это очень неверный пример песочницы.

Andy E 23.04.2012 19:38

var sCode = "this.alert ('FAIL')";

Leonard Pauli 04.07.2013 16:29

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

ADsafe makes it safe to put guest code (such as third party scripted advertising or widgets) on any web page. ADsafe defines a subset of JavaScript that is powerful enough to allow guest code to perform valuable interactions, while at the same time preventing malicious or accidental damage or intrusion. The ADsafe subset can be verified mechanically by tools like JSLint so that no human inspection is necessary to review guest code for safety. The ADsafe subset also enforces good coding practices, increasing the likelihood that guest code will run correctly.

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

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

B T 10.12.2015 22:39

Кроме того, он предотвращает доступ Любые к this, что совершенно неприемлемо. Вы не можете написать хороший javascript без использования this.

B T 10.12.2015 22:41

Внизу страницы Структура виджета ADsafe есть ссылка на код на GitHub.

Simon Lieschke 08.01.2016 04:09

@BT Я писал целые проекты без использования this. Избежать плохо названного параметра несложно.

soundly_typed 04.01.2017 05:10

@mindeavor Значит, вы полностью отказались от объектно-ориентированного JavaScript. Я бы сказал, что это недопустимо.

B T 05.01.2017 01:16

@BT Было бы глупо сказать, что завершение реальных проектов недопустимо. Но я сожалею, что начал это обсуждение, и должен уйти; здесь не место обсуждать такие вещи (извините). Я в твиттере, если вы хотите обсудить дальше.

soundly_typed 05.01.2017 02:12

@mindeavor Реальные проекты используют объектно-ориентированное программирование и this. Пожалуйста, откажитесь, ваш товар - мусор. Отказ this означает сбой правильного javascript. Это рецепт быть занозой в заднице. Да, вы это делаете мог, но по определению гостевой код - это не ты, это кто-то другой - гость. И сказать им, что они не могут использовать какую-либо библиотеку, использующую this, означает, что они покажут вам большой средний палец и никогда не интегрируются с вашим дерьмом.

B T 05.01.2017 04:11

@BT (я продолжу, поскольку это имеет отношение к вопросу) Каждый раз, когда вы запускаете код в чужой среде, вы сталкиваетесь с правилами и ограничениями. Я бы не назвал это неприемлемым. Может быть, «заноза в заднице». Но не недопустимо. В конце концов, для каждого использования this существует равный, эквивалентный способ, отличный от this (в конце концов, это всего лишь параметр).

soundly_typed 05.01.2017 08:00

как это использовать? я вообще не вижу никакой документации

slier 16.01.2017 05:49

@slier Я отредактировал свой ответ, подробно указав, где вы можете увидеть пример использования ADsafe.

Simon Lieschke 16.01.2017 23:54

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

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

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

Mike Samuel 30.09.2009 21:52

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

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

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

+1: Это выглядит действительно круто. Насколько безопасно выполнять код пользователя таким образом?

Konstantin Tarkus 10.11.2010 01:11

Очень безопасно. Ознакомьтесь с обновленной библиотекой на github.

Eli Grey 10.11.2010 23:55

этот проект все еще поддерживается? Вижу, не обновлялся уже более 2-х лет ...

Yanick Rochon 29.10.2012 05:12

Мне это нравится, за исключением того, что если вы хотите использовать песочницу, но все же разрешаете доступ к коду, чтобы сказать jQuery, это не сработает, поскольку веб-рабочие не позволяют манипулировать DOM.

Rahly 19.03.2015 00:55

Привет, Эли! Спасибо за отличную библиотеку, планируете ли вы ее поддерживать? У меня есть запрос на изменение для добавления функции отладки, что должно быть возможно при быстром просмотре кода. Пожалуйста, дай мне знать, что ты думаешь?

user1514042 15.05.2015 17:47

@Rahly: Все, что разрешено манипулировать DOM, по определению небезопасно. Как вы представляете себе доступ к DOM в песочнице?

Sasha Chedygov 15.06.2020 22:21

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

Rahly 16.06.2020 19:29

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

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.info( this, window, top , self, parent, this["jQuery"], (function(){return this;}()));'); 
// => Object {} undefined undefined undefined undefined undefined undefined 
console.info("return of this", sandboxcode('return this;', {window:"sanboxed code"})); 
// => Object {window: "sanboxed code"}

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

Тривиально вернуть window. sandboxcode('console.info((0,eval)("this"))')

Ry- 03.06.2015 22:44

Мне нужно выяснить, как предотвратить это

alejandro 05.07.2015 08:17

@alejandro Вы нашли способ предотвратить это?

Wilt 14.12.2015 16:07

Сломал себе голову, пока не понял, что вы можете сделать eval = 0 глобально, прежде чем вызывать песочницу (сохраняя исходную функцию во временном режиме), и тогда глобальные window.eval и eval не будут доступны. Следующий взлом, пожалуйста! Потому что я действительно рассматриваю этот вариант.

YoniXw 28.12.2018 03:33

Моя реализация просто добавляет: function sbx(s,p) {e = eval; eval = function(t){console.info("GOT GOOD")}; sandboxcode(s,p); eval =e}

YoniXw 28.12.2018 03:52

@YoniXw: Надеюсь, ты ни для чего его не использовал. Ни один такой подход никогда не сработает. (_=>_).constructor('return this')()

Ry- 10.03.2019 06:42

Нет хорошего способа сделать это, вы всегда можете сделать это, и у вас будет доступ с IIFE function sandboxcode(string, inject) { "use strict"; return (new Function(`const window = {}; var_this = this; const console = this.console; ${string}`)).call({console}); } sandboxcode('console.info(function(){ return this}())');.

alejandro 11.03.2019 03:13

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

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

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

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

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

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

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

vm2 не поддерживает среду выполнения в браузере. Однако он должен работать, если вы ищете код песочницы в приложении nodejs.

kevin.groat 13.06.2020 04:36

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

Другие вопросы по теме