Есть такой сервис для совместного редактирования текста — HackMD. Штука сама по себе полезная, но нас сегодня интересует ее реализация для установки на свой сервер — CodiMD. В ней нашли баг, позволяющий сделать код, который будет передаваться от пользователя к пользователю. Отличный случай, чтобы разобрать эксплуатацию неочевидных XSS и обсудить обход Content Security Policy (CSP).
Эту уязвимость нашел китайский исследователь Оранж Цай (Orange Tsai).
Официальная документация предлагает на выбор несколько вариантов разворачивания CodiMD. Один из них — Docker, его и будем использовать.
В первую очередь нужно клонировать репозиторий с конфигурационными файлами для запуска контейнера.
$ git clone https://github.com/hackmdio/docker-hackmd.git $ cd docker-hackmd
Теперь необходимо, чтобы при сборке устанавливалась нужная версия приложения. Уязвимы все версии до принятия пул-реквеста номер 1112 в основную ветку, то есть выпущенные до 29 декабря 2018 года. На момент написания статьи в файле конфигурации docker-compose значится версия 1.2.0.
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
.
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
.
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 год5240 р. Экономия 1400 рублей! |
1 месяц720 р. 25-30 статей в месяц |
Уже подписан?
Читайте также
Последние новости