Pull to refresh

Запускаем сторонний код в песочнице

JavaScript *
Как гласит статья из Википедии, Песочница — механизм для безопасного исполнения программ. Песочницы часто используют для запуска непротестированного кода, непроверенного кода из неизвестных источников, а также для запуска и обнаружения вирусов.

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

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

Начнем с тех видов атак, которые мы должны предотвратить.

Виды атак


Далее идет целая куча «векторов атак» с примерами (по ссылкам оригинальная статья), если вам не интересно расширить кругозор — пролистайте в конец поста. Атаки не кроссбраузерные.

GlobalObjectPoisoning — Отравление глобальных объектов
Array.prototype[4] = 'four';
var a1 = [];
alert('a1 has length ' + a1.length + ' but element 4 is ' + a1[4]);

var a2 = new Array(5);
alert('a2 has length ' + a2.length + ' and its element 4 is also ' + a2[4]);

EvalArbitraryCodeExecution — eval и конструктор Function могут выполнять сторонний код
eval('alert("your cookie is " + document.cookie)');

(new Function('alert("your cookie is " + document.cookie)'))();

ArgumentsMaskedByVar — массив аргументов функции маскируется под var arguments в Opera
(function (a, b, c) {
   var arguments = [1, 1, 1];
   alert('arguments[0] === ' + arguments[0] + ', a=' + a);
   arguments[0] = 2;
   alert('arguments[0] === ' + arguments[0] + ', a=' + a);
 })(0, 0, 0);

CrossScopeParameterModification — массив arguments позволяет менять свои параметры
(function (a) {
  arguments[0] = 1;
  alert('a=' + a);
})(0);>

ArgumentsExposesCaller — возможно вытащить аргументы внешней функции, используя caller
function untrusted() {
  alert('got function ' + untrusted.caller + ' : '
        + arguments.callee.caller.arguments[0]);
}

(function trusted() { untrusted(); })(4);

FunctionMemberCrossScopeParameterAccess — возможно заменить аргументы внешней функции, из внутренней функции
function f(a) {
  g();
  alert(a);
}

function g() {
  f.arguments[0] = 1;
}

f(0)

TypeofInconsistent — баг typeof с регулярными выражениями в вебкитах
'function' === (typeof /./)
'function' === (typeof alert)

InaccessibleLocalVariables — Локальные переменные могут быть недоступны при использовании catch (localVarName)
(function () {
   var arguments;
   alert('arguments === undefined: ' + (arguments === undefined));
 })();

(function () {
   var e;
   try {
     throw 1;
   } catch (e) {
   }
   alert('arguments === undefined: ' + (arguments === undefined));
 })();

(function () {
   var e = 1;
   try {
     throw 2;
   } catch (e) {
   }
   alert('e === 1 : ' + e);
 })();

CatchBlocksScopeBleed — возможно заменить глобальные переменные, используя catch
var a = 0;
(function () {
  try {
    throw 1;
  } catch (a) {
  }
})();
alert(a);  // alerts 1 on old FF

(function () {
  var a = 0;
  try {
    throw 1;
  } catch (a) {
  }
  alert(a);  // alerts 1 on IE 6 and 7 (IE 8 not tested)
})();

GlobalScopeViaThis — Возможно получить доступ к Global scope, используя this в функциях
(function () {
   alert('your cookie is ' + this.document.cookie);
 })();

setTimeout(
    function () {
      alert('your cookie is ' + this.document.cookie);
    }, 0)

DeleteUnmasksGlobals — Скрытые в with глобальные переменные могут быть вскрыты, используя delete
 with ({ document: null }) {
    delete document;
    alert('your cookie is ' + document.cookie);
  }

FunctionConstructor — Конструктор фукнции доступен через свойство 'constructor'
((function () {}).constructor)(
    'alert("document.cookie = " + document.cookie)')()

ObjectEvalArbitraryCodeExecution — Object.eval позволяет выполнить любой код в Firefox.
({}).eval('alert("Your cookie is " + document.cookie)')

ObjectWatch — Object.watch позволяет отравлять и получать любые внешние данные
function untrusted(o) {
  o.watch(
      'private_',
      function (obj, oldval, newval) {
        alert('untrusted got oldval ' + oldval + ' and newval ' + newval);
        return 'poisoned';  // substitute a bogus value
      });
}

// Trusted code
var o = { private_: 'old' };
untrusted(o);
o.private_ = 'new';
alert('private is now ' + o.private_);

ObjectToSourceLeaksPrivates — Object.toSource и uneval позволяет получить доступ к приватным свойствам
// Untrusted code
function untrusted(o) {
  // untrusted need not attempt to access private_ directly
  var privateValue = o.toSource().match(/private_:\s*(\d+)/)[1] * 1;
  alert('private value is ' + privateValue);
}

var o = { private_: 4 }
untrusted(o);

FunctionMethodsLeakGlobalScope — Function.call или Function.apply могут вскрыть глобальный объект, используя некоторые значения this.
(function () { alert(this === window); }).call(null);

(function () { alert(this === window); }).call(undefined);

alert(window === ([]).sort.call());

alert(window === ([]).reverse.call());

// Firefox2 only.  [https://bugzilla.mozilla.org/show_bug.cgi?id=406337]
var o = { valueOf: function () { return null } };
(function () { alert(this === window); }).call(o);

ConditionalCompilationComments — Условная компиляция позволяет злоумышленнику скрыть код.
/*@cc_on @*/ /*@if (1) alert(document.cookie) @end @*/

StringObfuscationIsEasy — Обфускация строк
(function () {
  var s = 'cons';
  s += 'tructor';
  (new ((function () {})[s])('alert("hello")'))();
})();

ParentCircumventsScoping — Используя __parent__ возможно получить доступ к переменными из любого scope
(function () {
  var alert = null;  // boilerplate that masks global alert

  (function () {  // untrusted code that can't access alert directly
    ({}).__parent__.alert('hello');
  })();

})();

JsControlFormatChars — Символ инвертации RTL или [:Cf:] может быть использовн для скрытия кода в комментариях.
<html>
  <body onload="

      /&#x200D;/.test(''); /*
      alert('hi');
      // */

      ">
  </body>
</html>

InconsistentlyReservedKeywords — Чувствительные к контексту слова: const constructor prototype
this['const'] = 0;
const
alert = f();                    // looks like an assignment to self.

function f() { return alert; }  // looks like a reference to an undefined local.

alert('hello world');

ErrorExposesParameterValues — Используя трейс стека ошибок можно получить доступ к парметрам, переданным во внешние функции
function skroob(luggageCombination) {
  darkHelmet();
}

function darkHelmet() {
  var combo = Number((new Error).stack.match(/skroob\((\d+)\)/)[1]);
  alert('Only an idiot would use that combination!: ' + combo);
}

skroob(1234);

RegexpsLeakMatchGlobally — Регулярки могут выполняться над последней строкой, отправленной в последнее регулярное выражение
// Privileged code
(function () {
  var queryString = document.location.search;  // Assume it's "?password=1234"

  function params() {
    return queryString.split(/[&?]/g);
  }

  if (params()[0] === 'debug=on') {
    // ...
  }
})();

// Unprivileged code without direct access to document.location
(function () {
  alert(/.*/.exec());
})();

EvalBreaksClosureEncapsulation — Вызов evel может сломать инкапсуляцию области видимости в старых версиях ff
function counter(i) {
  return function () { return ++i; };
}

var myCounter = counter(0);
alert(myCounter());  // => 1
eval('i = 4', myCounter);
alert(myCounter());  // => 5 on Firefox 2, 2 on other browsers

PostIncrementAndDecrementCanReturnNonNumber — Постинкремент и постдекримент может возвращать не число — способ полуения конструктора Function
 (function() {
    var c = 'constructor';
    var F = (function(){})[c++];  // Function constructor
    F('alert("toast")')();
  })();


Ещё целая куча атак, включая атаки из html, css code.google.com/p/google-caja/wiki/AttackVectors

Учитывая все эти атаки мы должны предоставить некий АПИ, чтобы плагин не работал автономно.

Фичи, которые мы должны предоставить плагину


1. Получение доступа к некоторым пользовательским данным.
2. Выполнение некоторых действий от лица пользователя.

Как же отгородиться от злоумышленника и дать хорошему разработчику все возможности?!

1. Модерация


image
Мы можем нанять команду модераторов, хорошо разбирающихся в коде, плюс ко всему принимать багрепорты от пользователей.
Недостатки: предельно дорого, и предельно не эффективно. Хорошим модераторам надо много платить, но всегда может найтись хакер искуснее любого модератора плюс человеческий фактор. Пользователи могут и не найти ошибку, плагин может делать свое черное дело незаметно от пользователя.
Достоинства: нет

2. Песочница в iFrame


Такой вариант используется в jsfiddle
1. Выносим скрипты пользователя на левый домен
2. Запрещаем выполнять alert prompt confirm
3. Пользователь в праве делать что угодно в своем фрэйме
4. АПИ предоставляем посредством postMessage (асинхронно)
Недостатки: плагины могут атаковать другие плагины, если они имеют общий домен. Асинхронный обмен. Могут найтись другие функции кроме alert prompt confirm, которые могут мешать пользователю. не все браузеры поддерживают postMessage
Достоинства: простая реализация сендбоксинга
Поробнее: dean.edwards.name/weblog/2006/11/sandbox

3. Песочница в WebWorkers


Воркеры отличная песочинца, они уже ограничены средой браузера
1. АПИ предостаавляем по средствам postMessage (асинхронно)
2. Мы должны создать библиотеку элементов интерфейса
3. Элементы будут реагировать на события асинхронно
Недостатки: разработчик плагина ограничен компонентами интерфейса. Асинхронный обмен и события. Не все браузеры поддерживают WebWorkers
Достоинства: простая реализация сендбоксинга
Пример: github.com/eligrey/jsandbox

4. Статический анализ и трансляция кода в код


Мы аналиируем исходный код, заменяем потенциально опасные коснтуркции на безопасные (css javascript html), либо не пропускаем потенциально опасные.
Недостатки: не выходе мы получаем не исходный код
Достоинства: работает везде

4.1. Douglas Crockford's ADsafe

image
ADsafe определяет подмножество JavaScript, которое достаточно мощное чтобы позволить гостевому коду выполнять важные действия, в то время как, предотвращает сценарии повреждающие или вторгающиеся в исходный код.
ADsafe удаляет возможности из JavaScript которые либо не безопасные, либо дают неконтролируемый доступ к компонентам браузера. В их список входят: Глобальные переменные, this, arguments, eval, with, arguments, callee, caller, constructor, eval, prototype, stack, unwatch, valueOf, watch, имена кончающиеся или начинающиеся на _, оператор [], Date и Math.random
Ограничения: Перед запуском код необходимо обработать JSLint, код должен быть в UTF-8, html id должны быть уникальными
Недостатки: излишние ограничения разработчика
Достоинства: работает везде
Подробнее можно посмотреть тут: www.adsafe.org
Поиграть с adsafe можно тут: www.jslint.com (включить флаг ADsafe)

4.2. Google Caja

image
В отличии от ADsafe Google Caja делает трансляцию (используя компилятор — «cajoler») JavaScript,HTML,CSS кода в безопасный (cajoled) JavaScript,HTML,CSS код. Сajoler проводит статический анализ для выявления потенциально опасных моментов, где статический анализ невозможен проверяет динамически — в процессе работы. Кроме этого cajoler делает виртуализацию кусков DOM, используя виртуальные iframe. Caja защищает от всех видов атак, описанных тут.
Недостатки: мы получаем измененный код
Достоинства: работает везде, не накладывает лишних ограничений на разработчика
Примеры трансляции кода

До
<script>
alert(document.cookie);
top.location = "http://www.thinkfu.com/evil.gif";
</script>

После
<script>
{
    ___.loadModule({
        'instantiate': function (___, IMPORTS___) {
            return ___.prepareModule({
                'instantiate': function (___, IMPORTS___) {
                    var dis___ = IMPORTS___;
                    var moduleResult___, x0___, x1___;
                    moduleResult___ = ___.NO_RESULT;
                    try {
                        {
                            (IMPORTS___.alert_v___ ? IMPORTS___.alert : ___.ri(IMPORTS___, 'alert')).i___((x0___ = IMPORTS___.document_v___ ? IMPORTS___.document : ___.ri(IMPORTS___, 'document'), x0___.cookie_v___ ? x0___.cookie : x0___.v___('cookie')));
                            moduleResult___ = (x1___ = IMPORTS___.top_v___ ? IMPORTS___.top : ___.ri(IMPORTS___, 'top'), x1___.location_w___ === x1___ ? (x1___.location = 'http://www.thinkfu.com/evil.gif') : x1___.w___('location', 'http://www.thinkfu.com/evil.gif'));
                        }
                    } catch (ex___) {
                        ___.getNewModuleHandler().handleUncaughtException(ex___, IMPORTS___.onerror_v___ ? IMPORTS___.onerror : ___.ri(IMPORTS___, 'onerror'), 'unknown', '2');
                    }
                    return moduleResult___;
                },
                'cajolerName': 'com.google.caja',
                'cajolerVersion': '4427',
                'cajoledDate': 1302010087717
            }).instantiate___(___, IMPORTS___), ___.prepareModule({
                'instantiate': function (___, IMPORTS___) {
                    var dis___ = IMPORTS___;
                    var moduleResult___;
                    moduleResult___ = ___.NO_RESULT; {
                        IMPORTS___.htmlEmitter___.signalLoaded();
                    }
                    return moduleResult___;
                },
                'cajolerName': 'com.google.caja',
                'cajolerVersion': '4427',
                'cajoledDate': 1302010087733
            }).instantiate___(___, IMPORTS___);
        },
        'cajolerName': 'com.google.caja',
        'cajolerVersion': '4427',
        'cajoledDate': 1302010087748
    });
}
</script>

Скрипт алертует «Untrusted gadget says: undefined» и не редиректит
Как видим получается далеко не исходный код, но конечный пользователь защищен от атак злоумышленника

Страница проекта: code.google.com/p/google-caja
Покрутить Google Caja можно тут: caja.appspot.com

Хотелось бы услышать какими способами сендбоксинга пользуетесь вы, есть ли у вас примеры внедрения?

Критика, вопросы и предложения приветствуются!

PS Извиняюсь за прорыв ката — парсер проспал
Tags: javascriptsandboxingcajaadsafeiframe
Hubs: JavaScript
Total votes 97: ↑92 and ↓5 +87
Comments 25
Comments Comments 25

Popular right now

Top of the last 24 hours