Содержание статьи
Стенд
Официальная документация предлагает на выбор несколько вариантов разворачивания 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

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

Остается просто поднять окружение при помощи docker-compose.
$ docker-compose up
И через несколько мгновений перед нами готовый стенд.

К слову, версия 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, '<').replace(/-->/g, '__HTML_COMMENT_END__').replace(/>/g, '>').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>" -->

Вот уж действительно «Упс!».
Логично предположить, что у нас имеется полноценная XSS, достаточно протянуть к ней script, и вот оно, исполнение кода на клиенте, у нас в руках. Но это не так, ведь тут в дело вступают политики CSP, которые разрешают выполнение кода на JavaScript только из доверенных источников.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»