Есть такой сервис для совместного редактирования текста — 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
Уязвимая версия HackMD в дефолтном конфиге docker-compose

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

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

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

$ docker-compose up

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

Готовый стенд с уязвимой версией CodiMD
Готовый стенд с уязвимой версией 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-тегов с помощью указания атрибутов к тегу комментария
Внедрение HTML-тегов с помощью указания атрибутов к тегу комментария

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

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

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

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

Подпишись на «Хакер» по выгодной цене!

Подписка позволит тебе в течение указанного срока читать ВСЕ платные материалы сайта. Мы принимаем оплату банковскими картами, электронными деньгами и переводами со счетов мобильных операторов. Подробнее о подписке

Оставить мнение

Check Also

Внутри x86-64 SystemV ABI. Как говорить с ядром Linux на его языке

Абстракция — основа программирования. Многие вещи мы используем, не задумываясь об их внут…