Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Есть такой сервис для совместного редактирования текста — HackMD. Штука сама по себе полезная, но нас сегодня интересует ее реализация для установки на свой сервер — CodiMD. В ней нашли баг, позволяющий сделать код, который будет передаваться от пользователя к пользователю. Отличный случай, чтобы разобрать эксплуатацию неочевидных XSS и обсудить обход Content Security Policy (CSP).
Официальная документация предлагает на выбор несколько вариантов разворачивания 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, '<').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>" -->
Внедрение HTML-тегов с помощью указания атрибутов к тегу комментария
Вот уж действительно «Упс!».
Логично предположить, что у нас имеется полноценная XSS, достаточно протянуть к ней script, и вот оно, исполнение кода на клиенте, у нас в руках. Но это не так, ведь тут в дело вступают политики CSP, которые разрешают выполнение кода на JavaScript только из доверенных источников.
Источник: xakep.ru