Чи можливий запуск JavaScript у браузері в браузері?


142

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

Наприклад, скажімо, я хочу надати API JavaScript для кінцевих користувачів, щоб вони визначали обробники подій, які будуть запускатися, коли трапляються "цікаві події", але я не хочу, щоб ці користувачі мали доступ до властивостей та функцій windowоб'єкта. Чи я в змозі це зробити?

У найпростішому випадку скажімо, я хочу завадити користувачам дзвонити alert. Я думаю про пару підходів:

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

Можливо, рішення, де сервер обробляє визначену користувачем функцію, а потім генерує зворотний виклик, який буде виконуватися клієнтом? Навіть якщо такий підхід працює, чи є кращі способи вирішити цю проблему?

Відповіді:


54

Google Caja - це перекладач від джерела до джерела, який "дозволяє розмістити ненадійні сторонні HTML та JavaScript на своїй сторінці та все ще захистити".


5
Швидкий тест показує, що Caja не в змозі захистити браузер від нападів на процесор, наприклад while (1) {}--- він просто зависає. Так само a=[]; while (1) { a=[a,a]; }.
Давид Дано

5
Так, відмова в наданні послуги не входить у сферу дії: code.google.com/p/google-caja/isissue/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

4
@BT Я писав цілі проекти без використання this. Уникнути погано названого параметра не важко.
soundly_typed

2
@BT Дурно було б сказати, що завершення реальних проектів неприпустимо. Але я шкодую, що розпочав це обговорення, і мушу відмовитися; це не місце для обговорення таких речей (вибачте). Я на Twitter, якщо ви хочете обговорити далі.
soundly_typed

1
@BT (я продовжую, оскільки це стосується питання) Щоразу, коли ви запускаєте код у чужому середовищі, у вас виникнуть правила та обмеження. Я б не назвав це неприйнятним. Можливо, "біль у попі". Але не неприйнятно. Зрештою, для кожного використання thisіснує рівнозначний рівнозначний thisспосіб - це все-таки лише параметр.
soundly_typed

24

Я створив бібліотеку пісочниці під назвою 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
цей проект все ще підтримується? Я бачу, що він не оновлювався вже понад 2 роки ...
Yanick Rochon

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

Привіт Елі - спасибі за чудовий ліб, чи плануєш ти його підтримувати? У мене є запит на зміну для додавання налагоджувальної функції - що, швидкий перегляд коду, повинен бути можливим. Будь ласка, дайте мені знати, що ви думаєте?
користувач1514042

8

Я думаю, що js.js тут варто згадати. Це інтерпретатор JavaScript, написаний на JavaScript.

Це приблизно в 200 разів повільніше, ніж рідний JS, але його природа робить його ідеальним середовищем з пісочницею. Ще один недолік - його розмір - майже 600 кбіт, що може бути прийнятним для настільних ПК у деяких випадках, але не для мобільних пристроїв.


7

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

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


4

Усі постачальники браузерів та специфікація HTML5 працюють над фактичним властивістю пісочної скриньки, щоб дозволити рамки з пісочним кадром - але це все ще обмежено деталізацією iframe.

Взагалі, жодна ступінь регулярних виразів тощо не може безпечно оперувати довільного користувача за умови JavaScript, оскільки це вироджує проблему зупинки: - /


2
Чи можете ви пояснити, як це вироджується до проблеми зупинки?
hdgarrood

2
Теоретична неможливість вирішити задачу зупинки дійсно стосується лише статичного аналізу коду. Пісочниці можуть виконувати такі дії, як примусові обмеження часу для вирішення проблеми зупинки.
Авендха

4

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


2

Неприємний спосіб, але, можливо, це працює для вас, я взяв усі глобалі та переробив їх в області пісочниці, а також додав суворий режим, щоб вони не могли отримати глобальний об’єкт за допомогою анонімної функції.

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"))')
Ри-

Мені доведеться розібратися, як запобігти цьому
алехандро

@alejandro Ви знайшли спосіб запобігти цьому?
Wilt

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')()
Ри-

1

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


1

Станом на 2019 рік vm2 виглядає як найпопулярніше та регулярно оновлюване рішення цієї проблеми.


vm2 не підтримує час виконання у браузері. Однак це має працювати, якщо ви шукаєте пісочний код у додатку nodejs.
kevin.groat

0

За допомогою NISP ви зможете провести оцінку в пісочному режимі. Хоча вираз, який ви пишете, не є точно JS, натомість ви будете писати s-вирази. Ідеально підходить для простих DSL, які не вимагають широкого програмування.


-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), "це" буде об'єктом вікна.
mbarkhau

5
Перший приклад не провалюється, це дуже недійсний приклад пісочниці.
Енді Е

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
Якщо хтось намагається зробити щось шкідливе, простий регулярний вираз просто не може цього зробити - take (function () {this ["loca" + "tiona "] =" example.com ";}) () Загалом, якщо ви не можете довіряти своїм користувачам (що стосується будь-якого веб-сайту, на якому довільний користувач може додавати вміст), блокуючи всі js, необхідно.
olliej

Я раніше використовував щось подібне. Це не ідеально, але ви отримуєте більшу частину шляху туди.
Сугендран

olliej, ти маєш рацію щодо обмежень такої методики. Як щодо перезапису глобальних змінних, таких як <code> var window = null, document = null, this = {}; </code>?
Димитрій

Димитрій Z, перезапис цих змінних заборонено [в деяких браузерах]. Також перевірте моє рішення у списку відповідей - воно працює.
Сергій Ілинський

-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);
            }
        }
    };
}

Отже, я можу примірник Sandbox і використовувати його Execute () для запуску коду. Крім того, всі нові задекларовані змінні в коді eval'd в кінцевому підсумку будуть прив’язані до області Execute (), тому не буде зіткнення імен або псування з існуючим кодом.

Хоча глобальні об'єкти все ще будуть доступними, ті, які повинні залишатися невідомими для пісочного коду, повинні бути визначені як проксі в об'єкті Sandbox :::

Сподіваюся, це працює для вас.


8
Це нічого не пісочниці. Евальований код може таким чином видалити членів і потрапити до глобальної області, або схопити посилання на глобальний діапазон ghe, виконуючи (function () {return this;}) ()
Майк Самуель

-6

Ви можете зафіксувати код користувача у функції, яка визначає заборонені об'єкти як параметри - це буде, undefinedколи викликається:

(function (alert) {

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

}) ();

Звичайно, розумні зловмисники можуть обійти це, оглянувши Javascript DOM і виявивши неперекритий об'єкт, що містить посилання на вікно.


Інша ідея - сканування коду користувача за допомогою такого інструменту, як jslint . Переконайтеся, що в ньому встановлено відсутність попередньо встановлених змінних (або: потрібні тільки змінні), а потім, якщо будь-які глобальні параметри встановлені чи доступні, не дозволяйте використовувати сценарій користувача. Знову ж таки, може бути вразливим ходіння по DOM - об’єкти, які користувач може сконструювати за допомогою літералів, можуть мати неявні посилання на об’єкт вікна, до якого можна отримати доступ, щоб уникнути пісочної скриньки.


2
Якщо користувач введев window.alert замість простого оповіщення, він обійшов би цю межу.
Квентін

@Dorward: так, отже, "заборонені об'єкти". wrunsby повинен вирішити, до яких об’єктів користувач не має доступу та розмістити їх у списку параметрів.
Джон Міллікін

Є лише один об’єкт - вікно. Якщо ви не блокуєте доступ до нього, то все доступне через нього. Якщо ви заблокуєте його, то скрипт не може отримати доступ до будь-яких його властивостей (оскільки говоріння попередження замість window.alert просто передбачає вікно).
Квентін

@Doward: це не так, ви б заблокували window.alert, але попередження все одно спрацює, спробуйте. Це тому, що вікно - це також глобальний об’єкт. Потрібно буде заблокувати вікно та будь-яке властивість чи метод вікна, до якого ви не хотіли доступу до коду користувача.
AnthonyWJones
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.