Client-Side Fundamental
  • Добро пожаловать
  • Глава 1 - Начало работы с XSS
    • Браузерная модель безопасности
    • Знакомимся с уязвимостью XSS
    • Более глубокое понимание XSS
    • Опасный псевдопротокол javascript
  • Глава 2 - Защита и Обход для XSS
    • Первая линия обороны от XSS - Sanitization
    • Вторая линия обороны от XSS - CSP (Content Security Policy)
    • Третья линия обороны против XSS - сокращение области воздействия
    • Последние методы защиты от XSS - Trusted Types и встроенный Sanitizer API
    • Обход защитных мер - Обычные способы обхода CSP
    • Обход защитных мер - Mutation XSS
    • Самая опасная XSS - Universal XSS
  • Глава 3 - Атаки без JavaScript
    • Кто сказал, что для атаки обязательно выполнять JavaScript?
    • Prototype Pollution - Эксплуатация цепочки прототипов
    • Может ли HTML влиять на JavaScript - Введение в DOM clobbering
    • Template Injection in Frontend - CSTI
    • CSS Injection - Атака с использованием только CSS (Часть 1)
    • CSS Injection - Атака с использованием только CSS (Часть 2)
    • Можно ли атаковать, используя только HTML
  • Глава 4 - Межсайтовые атаки
    • Same-origin Policy и Same-Site
    • Введение в Cross-Origin Resource Sharing (CORS)
    • Проблемы Cross-Origin безопасности
    • Cross-Site Request Forgery (CSRF)
    • Спаситель от CSRF - Same-site cookie
    • От same-site до главного site
    • Интересная и практичная Cookie Bomb
  • Глава 5 - Другие интересные темы
    • То, что вы видите, это не то, что вы получаете - Clickjacking
    • Эксплуатация MIME Sniffing
    • Атаки на цепочку поставок во фронтенде - Attacking Downstream from Upstream
    • Атаки на веб-фронтенд в Web3
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 1)
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 2)
Powered by GitBook
On this page
  • Basic Flow of Sanitizers
  • Browser's "Considerate" Feature
  • The Magic of HTML
  • Объединяем все вместе
  • Решение проблемы
  • Заключение
  1. Глава 2 - Защита и Обход для XSS

Обход защитных мер - Mutation XSS

PreviousОбход защитных мер - Обычные способы обхода CSPNextСамая опасная XSS - Universal XSS

Last updated 8 months ago

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

Но есть ли у этих библиотек проблемы? Это возможно, и на самом деле это уже происходило. Одна из распространенных атак на санитайзеры называется мутационный XSS, также известный как mXSS.

Прежде чем понять mXSS, давайте посмотрим, как обычно работают санитайзеры.

Basic Flow of Sanitizers

Исходя из нашего предыдущего опыта, входными данными для санитайзера является строка, содержащая HTML, и выходными данными также является строка, содержащая HTML. Вот пример того, как это используется:

const inputHtml = '<h1>hello</h1>'const safeHtml = sanitizer.sanitize(inputHtml)document.body.innerHTML = safeHtml

Итак, как работает санитайзер внутри? На самом деле его внутренняя работа очень похожа на санитайзер, который мы реализовали с использованием BeautifulSoup:

1. Преобразовать inputHtml в DOM-дерево. 2. Удалить недопустимые узлы и атрибуты на основе файла конфигурации. 3. Преобразовать DOM-дерево обратно в строку. 4. Вернуть строку.

Этот процесс, кажется, не вызывает проблем, но дьявол кроется в деталях. Что, если "HTML, который кажется безопасным, на самом деле таковым не является"? Подождите, разве мы уже не санитизировали его? Как он может быть небезопасен? Давайте сначала рассмотрим пример.

Browser's "Considerate" Feature

Браузер - это внимательное программное обеспечение, которое, чтобы справиться с различными ситуациями и соответствуя спецификациям, может не отображать HTML именно так, как вы его видите. Например, рассмотрим следующий пример:

<!DOCTYPE html><html><body>  <div id=content></div>  <script>    content.innerHTML = '<table><h1>hello</h1></table>'  </script></body></html>

Поместить <h1> внутрь <table> кажется нормальным, но если вы откроете эту веб-страницу, заметите:

Структура HTML изменилась!

Она становится:

<h1>hello</h1><table></table>

<h1>, который должен был быть внутри <table>, "выпрыгивает" из него. Это происходит, потому что браузер, исходя из спецификации HTML, определяет, что <h1> не должен быть внутри <table>, поэтому он любезно убирает его. Исходя из истории развития веба, для браузеров нормально пытаться исправить недействительный HTML. В конце концов, это лучше, чем выбрасывание ошибки или отображение пустой страницы.

Это поведение "HTML-строки изменяются браузером при рендеринге" называется мутацией. И XSS, достигнутый за счет использования этого поведения, естественно, называется мутационным XSS.

Рассмотрим еще один пример:

<!DOCTYPE html><html><body>  <div id=content></div>  <script>    content.innerHTML = '<svg><p>hello</svg>'  </script></body></html>

Результат рендеринга:

Браузер считает, что <p> не должен быть внутри <svg>, поэтому он перемещает <p> из <svg> и также исправляет HTML, добавляя </p>.

А как насчет этого еще более странного примера? На этот раз, вместо <p>, это </p>:

<!DOCTYPE html><html><body>  <div id=content></div>  <script>    content.innerHTML = '<svg></p>hello</svg>'  </script></body></html>

Результат:

<svg><p></p>hello</svg>

Браузер автоматически исправляет </p>, добавляя перед ним <p>, но тег все равно остается внутри <svg>.

(Примечание: Поведение браузера Chrome было исправлено, и теперь будет <svg></svg><p></p>hello. Поэтому, в настоящее время, мы не можем воспроизвести эту ситуацию, но продолжим.)

Теперь, происходит что-то интересное. Если мы берем <svg><p></p>hello</svg> и передаем его innerHTML, каков будет результат?

<!DOCTYPE html><html><body>  <div id=content></div>  <script>    content.innerHTML = '<svg><p></p>hello</svg>'    console.log(content.innerHTML)  </script></body></html>

Результат:

<svg></svg><p></p>hello

Не только <p>, но даже следующий "hello" выпрыгивает. Все, что было первоначально внутри <svg>, теперь находится вне его.

Итак, как эта серия изменений помогает нам обойти санитайзер? Для этого требуется сочетание с процессом санитайзера, упомянутым ранее.

Допустим, наш inputHtml выглядит так: <svg></p>hello</svg>. Первый шаг санитайзера - преобразовать его в DOM-дерево. Исходя из предыдущего эксперимента, это становится:

<svg>  <p></p>  hello</svg>

Он выглядит абсолютно нормально, ничего не нужно фильтровать. Следующий шаг - преобразовать DOM-дерево обратно в строку, что дает: <svg><p></p>hello</svg>.

Далее, команда фронтенд-разработчиков получает safeHtml и выполняет document.body.innerHTML = safeHtml. Получающийся HTML выглядит следующим образом:

<svg></svg><p></p>hello

Для санитайзера <p> и "hello" находятся внутри SVG, но финальный результат другой. Они размещены снаружи. Таким образом, через этую мутацию мы можем заставить любой элемент выпрыгнуть из <svg>.

Вы может спросить: "И что? В чем польза этого?" Вот где дело становится интересным

The Magic of HTML

Тег <style> - это волшебный тег, потому что все, что находится внутри этого тега, интерпретируется как текст. Например:

<!DOCTYPE html><html><body>  <style>    <a id="test"></a>  </style></body></html>

Интерпретируется как:

Черный текст соответственно представляет собой текст .

Но вот интересная часть. Если мы добавим внешний <svg>, то браузер интерпретирует его по-другому, и все меняется. Текущий исходный HTML-код:

<!DOCTYPE html><html><body>  <svg>    <style>      <a id="test"></a>    </style>  </svg></body></html>

В результате интерпретируется:

Тег <a> внутри <style> становится настоящим HTML-элементом, а не просто текстом.

Еще интереснее то, что вы можете построить следующий HTML:

<svg>  <style>    <a id="</style><img src=x onerror=alert(1)>"></a>  </style></svg>

И это будет отображаться как:

Здесь мы просто добавили идентификатор <a> со значением </style><img src=x onerror=alert(1)>. Хотя он содержит </style>, он не закрывает предыдущий <style>. Вместо этого, он становится частью атрибута id. То же самое касается тега <img>. Это не новый тег, а часть содержимого атрибута.

Однако, если мы удалим <svg> и изменить на:

<style>  <a id="</style><img src=x onerror=alert(1)>"></a></style>

Поскольку <a> больше не является элементом, а просто текстом, у него нет атрибутов. Таким образом, </style> здесь закроет предыдущий <style>, в результате чего получится:

Тег <img> внутри идентификатора <a> изначально был лишь частью содержимого атрибута, но теперь, из-за предшествующего </style>, он представлен как собственный HTML элемент.

Исходя из приведенных выше экспериментов, можно сделать вывод, что наличие <svg> в <style> важно, поскольку это влияет на интерпретацию браузером.

Объединяем все вместе

Мы упомянули в начале об изменении поведения браузера, которое позволяет нам "вытащить все элементы из <svg>". Мы также упоминали, что "наличие <svg> для <style> важно". Комбинируя эти два понятия, мы можем получить mXSS.

19 сентября 2019 года DOMPurify выпустила версию 2.0.1, чтобы исправить уязвимость mXSS, которая обошла проверки с помощью мутаций. Проблемный в данном случае код был:

<svg></p><style><a id="</style><img src=1 onerror=alert(1)>">

После преобразования этого в DOM-древо, структура становится:

<svg>  <p></p>  <style>    <a id="</style><img src=1 onerror=alert(1)>"></a>  </style></svg>

Браузер делает здесь несколько вещей:

1. Преобразует </p> в <p></p> 2. Автоматически закрывает теги <svg>, <style>, и <a>

Затем DOMPurify проверяет на основе этого DOM-дерева. Поскольку <svg>, <p>, <style>, и <a> — все это разрешенные теги, и id является разрешенным атрибутом, все в порядке. Поэтому она возвращает сериализованный результат

<svg>  <p></p>  <style>    <a id="</style><img src=1 onerror=alert(1)>"></a>  </style></svg>

Затем пользовательская программа передает эту строку в innerHTML, и происходят вышеупомянутые мутации. Все теги выбрасываются из <svg>, в результате чего получается:

<svg></svg><p></p><style><a id="</style><img src=1 onerror=alert(1)>"></a></style>

Поскольку <style> также выбрасывается, элемент <a> больше не существует и становится простым текстом. В результате, </style> преждевременно закрывается, что приводит к тому, что скрытый <img> становится настоящим HTML-элементом внутри содержимого атрибута. Это в конечном итоге приводит к XSS.

Решение проблемы

Конечный результат заключался в том, что в спецификацию было добавлено новое правило, и Chromium исправил эту уязвимость на основе нового правила.

Так что потом похожие уязвимости больше не встречались, и все жили счастливо и долго… или все же нет?

Нет, позже было обнаружено, что у DOMPurify был более сложный метод обхода, но после его исправления он стал ещё сильнее и проблемы в основном не возникали.

Человек, который обнаружил эту проблему, был Michał Bentkowski, старший эксперт по безопасности в области разработки веб-безопасности. Он сообщал о различных больших и маленьких проблемах и имеет глубокое понимание парсинга HTML и различных механизмов. Позже мы увидим некоторые из классических уязвимостей, которые он сообщал.

Если вы хотите углубиться в эту проблему, вы можете обратиться к статьям, которые он написал ранее. Мои знания о mXSS происходят от него:

Заключение

Когда я впервые столкнулся с mXSS, я был запутан и не полностью его понял. Для написания этой статьи я снова прошёл через контекст и попробовал сам, и тогда мне показалось, что я понял, что происходит. Понимание его концепции не сложно, но вникнуть во все детали потребует немного больше времени. Более того, обнаруженные уязвимости уже были исправлены, поэтому их невозможно воспроизвести в текущих браузерах, что немного проблематично.

Но в целом, я думаю, что mXSS - это более продвинутая тема внутри XSS. Она включает в себя спецификацию HTML, парсинг браузера и работу санитайзеров. Это нормально, что на её понимание требуется немного больше времени.

Чтобы исправить эту проблему, DOMPurify добавил в код, чтобы предотвратить подверженность mXSS.

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

Таким образом, эта проблема стала вопросом исправления самой спецификации, и они открыли вопрос в репозитории спецификации:

1. 2. 3.

проверку
Issue 1005713: Security: Parser bug can introduce mXSS and HTML sanitizers bypass
Unmatched p or br inside foreign context needs a special parser rule #5113
Write-up of DOMPurify 2.0.0 bypass using mutation XSS
Mutation XSS via namespace confusion – DOMPurify < 2.0.17 bypass
HTML sanitization bypass in Ruby Sanitize < 5.2.1