Документный червь. Эксплуатируем необычную XSS и обходим CSP на примере CodiMD

Есть такой сервис для совместного редактирования текста — HackMD. Штука сама по себе полезная, но нас сегодня интересует ее реализация для установки на свой сервер — CodiMD. В ней нашли баг, позволяющий сделать код, который будет передаваться от пользователя к пользователю. Отличный случай, чтобы разобрать эксплуатацию неочевидных XSS и обсудить обход Content Security Policy (CSP).

INFO

Эту уязвимость нашел китайский исследователь Оранж Цай (Orange Tsai).

Стенд

Официальная документация предлагает на выбор несколько вариантов разворачивания CodiMD. Один из них — Docker, его и будем использовать.

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

$ git clone https://github.com/hackmdio/docker-hackmd.git
$ cd docker-hackmd

Теперь необходимо, чтобы при сборке устанавливалась нужная версия приложения. Уязвимы все версии до принятия пул-реквеста номер 1112 в основную ветку, то есть выпущенные до 29 декабря 2018 года. На момент написания статьи в файле конфигурации docker-compose значится версия 1.2.0.

docker-compose.yml
app:
    ...
    image: hackmdio/hackmd:1.2.0
Уязвимая версия HackMD в дефолтном конфиге docker-compose

Эта версия вышла 27 сентября 2018 года, что меня вполне устраивает.

Дата выхода контейнера HackMD версии 1.2.0

Остается просто поднять окружение при помощи docker-compose.

$ docker-compose up

И через несколько мгновений перед нами готовый стенд.

Готовый стенд с уязвимой версией CodiMD

К слову, версия 1.2.1 тоже уязвима, поэтому можно использовать и ее.

Детали уязвимости

Одна из особенностей HackMD — риалтаймовое обновление превью. То есть разметка Markdown рендерится в HTML, который выводится в окно слева от исходного кода.

Обновление документа на лету

Так как страница клиента изменяется на лету и рендерит введенные пользователем данные, то защита от XSS становится очень актуальной задачей. Ведь Markdown — это надстройка над HTML, соответственно, помимо разметки Markdown, в документе можно использовать и другие теги. А скрипты — это, в свою очередь, валидный HTML.

HackMD написан с использованием Node.js и для этих целей привлекает библиотеку XSS, первая версия которой вышла аж семь лет назад и с тех пор стабильно обновляется. Давай посмотрим, как она применяется при рендеринге пользовательского содержимого. Для этого заглянем в файл render.js.

/codimd-1.2.0/public/js/render.js
11: var whiteList = filterXSS.whiteList
...
35: var filterXSSOptions = {
36:   allowCommentTag: true,
37:   whiteList: whiteList,
38:   escapeHtml: function (html) {
39:     // Allow HTML comment in multiple lines
40:     return html.replace(/<(?!!--)/g, '&lt;').replace(/-->/g, '__HTML_COMMENT_END__').replace(/>/g, '&gt;').replace(/__HTML_COMMENT_END__/g, '-->')
...
68: function preventXSS (html) {
69:   return filterXSS(html, filterXSSOptions)
70: }
71: window.preventXSS = preventXSS
72:
73: module.exports = {
74:   preventXSS: preventXSS
75: }

Библиотека XSS предоставляет разработчикам возможность гибкой настройки фильтрации. Это делается при помощи таких опций, как, например, allowCommentTag или whiteList, и колбэков — onTagAttr и onIgnoreTagAttr. Здесь особый интерес представляет onIgnoreTag.

/codimd-1.2.0/public/js/render.js
42:   onIgnoreTag: function (tag, html, options) {
43:     // Allow comment tag
44:     if (tag === '!--') {
45:             // Do not filter its attributes
46:       return html
47:     }
48:   },

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

<!-- comment, aga -->
Комментарии переносятся в отрендеренную страницу без фильтрации

Это полезно, если нужно сохранить полную структуру документа. Однако так ли это безопасно?

По большому счету конструкция <!-- — это тоже тег, и у него могут быть атрибуты. Поэтому попробуем классическую атаку с внедрением HTML-кода в них, ведь они не фильтруются (// Do not filter its attributes). 😉

<!-- attr="value--> <b>Oops</b>" -->
Внедрение HTML-тегов с помощью указания атрибутов к тегу комментария

Вот уж действительно «Упс!».

Логично предположить, что у нас имеется полноценная XSS, достаточно протянуть к ней script, и вот оно, исполнение кода на клиенте, у нас в руках. Но это не так, ведь тут в дело вступают политики CSP, которые разрешают выполнение кода на JavaScript только из доверенных источников.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


aLLy: @iamsecurity Специалист по информационной безопасности в ONsec. Research, ethical hacking and Photoshop.