Мне интересно, можно ли изолировать JavaScript, работающий в браузере, для предотвращения доступа к функциям, которые обычно доступны для кода JavaScript, запущенного на странице HTML.
Например, предположим, что я хочу предоставить конечным пользователям JavaScript API, чтобы они могли определять обработчики событий, запускаемые при возникновении «интересных событий», но я не хочу, чтобы эти пользователи получали доступ к свойствам и функциям объекта window. Могу ли я это сделать?
В простейшем случае, допустим, я хочу запретить пользователям вызывать alert. Я могу придумать несколько подходов:
window.alert в глобальном масштабе. Я не думаю, что это был бы допустимый подход, потому что другой код, работающий на странице (то есть материал, не созданный пользователями в их обработчиках событий), может захотеть использовать alert.Возможно, сработает решение, в котором сервер обрабатывает пользовательскую функцию, а затем генерирует обратный вызов для выполнения на клиенте? Даже если этот подход работает, есть ли лучшие способы решить эту проблему?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете заключить код пользователя в функцию, которая переопределяет запрещенные объекты как параметры - тогда при вызове это будет undefined:
(function (alert) {
alert ("uh oh!"); // User code
}) ();
Конечно, умные злоумышленники могут обойти это, проверив Javascript DOM и найдя непереопределенный объект, содержащий ссылку на окно.
Другая идея - сканировать код пользователя с помощью такого инструмента, как jslint. Убедитесь, что в нем нет предустановленных переменных (или: только те переменные, которые вам нужны), а затем, если установлены или доступны какие-либо глобальные переменные, не позволяйте использовать скрипт пользователя. Опять же, могут быть уязвимы для обхода DOM - объекты, которые пользователь может создавать с помощью литералов, могут иметь неявные ссылки на объект окна, к которому можно получить доступ, чтобы выйти из песочницы.
@Dorward: да, отсюда и «запрещенные объекты». wrunsby должен решить, к каким объектам пользователю не разрешен доступ, и поместить их в список параметров.
Есть только один объект - окно. Если не заблокировать к нему доступ, то через него доступно все. Если вы заблокируете его, скрипт не сможет получить доступ ни к одному из его свойств (поскольку слово alert вместо window.alert просто подразумевает окно).
@Doward: это не тот случай, когда вы заблокируете window.alert, но предупреждение все равно будет работать, попробуйте. Это потому, что окно также является глобальным объектом. Потребуется заблокировать окно и любое свойство или метод окна, к которому пользовательский код не должен получать доступ.
Откуда взялся этот пользовательский 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, вы правы насчет ограничений такой техники. Как насчет перезаписи глобальных переменных, таких как <code> var window = null, document = null, this = {}; </code>?
Дмитрий З, перезапись этих переменных запрещена [в некоторых браузерах]. Также проверьте мое решение в списке ответов - оно работает.
Все поставщики браузеров и спецификация HTML5 работают над фактическим свойством песочницы, чтобы разрешить изолированные окна iframe, но оно по-прежнему ограничено детализацией iframe.
В общем, никакие регулярные выражения и т. д. Не могут безопасно дезинфицировать произвольный предоставленный пользователем JavaScript, поскольку он вырождается в проблему остановки:
Вы можете объяснить, как это перерастает в проблему остановки?
Теоретическая невозможность решения проблемы остановки действительно применима только к статическому анализу кода. Песочницы могут делать такие вещи, как ограничение времени, чтобы справиться с проблемой остановки.
Google Caja - это переводчик от источника к источнику, который «позволяет вам размещать ненадежные сторонние HTML и JavaScript встроенными в вашу страницу и при этом оставаться в безопасности».
Быстрый тест показывает, что Caja не может защитить браузер от атак ЦП, таких как while (1) {}, - он просто зависает. Аналогично a=[]; while (1) { a=[a,a]; }.
Да, отказ в обслуживании выходит за рамки: code.google.com/p/google-caja/issues/detail?id=1406
Проект будет прекращен Google 31 января 2021 года. Вместо этого они рекомендуют людям перейти в библиотеку Closure (github.com/google/closure-library).
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» будет объектом окна.
Первый пример не подводит, это очень неверный пример песочницы.
var sCode = "this.alert ('FAIL')";
Взгляните на 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?
Кроме того, он предотвращает доступ Любые к this, что совершенно неприемлемо. Вы не можете написать хороший javascript без использования this.
Внизу страницы Структура виджета ADsafe есть ссылка на код на GitHub.
@BT Я писал целые проекты без использования this. Избежать плохо названного параметра несложно.
@mindeavor Значит, вы полностью отказались от объектно-ориентированного JavaScript. Я бы сказал, что это недопустимо.
@BT Было бы глупо сказать, что завершение реальных проектов недопустимо. Но я сожалею, что начал это обсуждение, и должен уйти; здесь не место обсуждать такие вещи (извините). Я в твиттере, если вы хотите обсудить дальше.
@mindeavor Реальные проекты используют объектно-ориентированное программирование и this. Пожалуйста, откажитесь, ваш товар - мусор. Отказ this означает сбой правильного javascript. Это рецепт быть занозой в заднице. Да, вы это делаете мог, но по определению гостевой код - это не ты, это кто-то другой - гость. И сказать им, что они не могут использовать какую-либо библиотеку, использующую this, означает, что они покажут вам большой средний палец и никогда не интегрируются с вашим дерьмом.
@BT (я продолжу, поскольку это имеет отношение к вопросу) Каждый раз, когда вы запускаете код в чужой среде, вы сталкиваетесь с правилами и ограничениями. Я бы не назвал это неприемлемым. Может быть, «заноза в заднице». Но не недопустимо. В конце концов, для каждого использования this существует равный, эквивалентный способ, отличный от this (в конце концов, это всего лишь параметр).
как это использовать? я вообще не вижу никакой документации
@slier Я отредактировал свой ответ, подробно указав, где вы можете увидеть пример использования ADsafe.
Я работал над упрощенной песочницей 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;}) ()
Я создал библиотеку песочницы под названием 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: Это выглядит действительно круто. Насколько безопасно выполнять код пользователя таким образом?
Очень безопасно. Ознакомьтесь с обновленной библиотекой на github.
этот проект все еще поддерживается? Вижу, не обновлялся уже более 2-х лет ...
Мне это нравится, за исключением того, что если вы хотите использовать песочницу, но все же разрешаете доступ к коду, чтобы сказать jQuery, это не сработает, поскольку веб-рабочие не позволяют манипулировать DOM.
Привет, Эли! Спасибо за отличную библиотеку, планируете ли вы ее поддерживать? У меня есть запрос на изменение для добавления функции отладки, что должно быть возможно при быстром просмотре кода. Пожалуйста, дай мне знать, что ты думаешь?
@Rahly: Все, что разрешено манипулировать DOM, по определению небезопасно. Как вы представляете себе доступ к DOM в песочнице?
В теории? Вы должны создать фрагмент документа, который находится в тюрьме, где любые манипуляции / обходы ограничены деревом фрагментов.
Уродливый способ, но, возможно, это сработает для вас, я взял все глобальные объекты и переопределил их в области песочницы, а также добавил строгий режим, чтобы они не могли получить глобальный объект с помощью анонимной функции.
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"))')
Мне нужно выяснить, как предотвратить это
@alejandro Вы нашли способ предотвратить это?
Сломал себе голову, пока не понял, что вы можете сделать eval = 0 глобально, прежде чем вызывать песочницу (сохраняя исходную функцию во временном режиме), и тогда глобальные window.eval и eval не будут доступны. Следующий взлом, пожалуйста! Потому что я действительно рассматриваю этот вариант.
Моя реализация просто добавляет: function sbx(s,p) {e = eval; eval = function(t){console.info("GOT GOOD")}; sandboxcode(s,p); eval =e}
@YoniXw: Надеюсь, ты ни для чего его не использовал. Ни один такой подход никогда не сработает. (_=>_).constructor('return this')()
Нет хорошего способа сделать это, вы всегда можете сделать это, и у вас будет доступ с 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}())');.
Думаю, здесь стоит упомянуть js.js. Это интерпретатор JavaScript, написанный на JavaScript.
Он примерно в 200 раз медленнее, чем нативный JS, но по своей природе идеален для песочницы. Еще один недостаток - его размер - почти 600 Кб, что в некоторых случаях может быть приемлемо для настольных компьютеров, но не для мобильных устройств.
Как упоминалось в других ответах, достаточно заблокировать код в изолированном iframe (без отправки его на серверную сторону) и обмениваться сообщениями. Я бы предложил взглянуть на небольшая библиотека, который я создал в основном из-за необходимости предоставить некоторый API для ненадежного кода, как описано в вопросе: есть возможность экспортировать конкретный набор функций прямо в песочницу, где ненадежный код запускается. Также есть демонстрация, которая выполняет код, отправленный пользователем в песочнице:
Улучшенная версия кода песочницы веб-воркеров @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.
С НИСП вы сможете выполнять оценку в песочнице. Хотя написанное вами выражение - это не совсем JS, вместо этого вы напишете s-выражения. Идеально подходит для простых DSL, не требующих обширного программирования.
Если пользователь введет window.alert вместо простого предупреждения, он обойдет это ограничение.