Всегда хотел взломать Хабр. Мечта такая, но как-то руки не доходили. И вот, вдохновившись статьей о праведном взломе через iframe src , я, как и автор поста @Maxchagin, решил исследовать функционал Хабра на предмет уязвимостей.
Начать решил с нового редактора, рассуждая следующим образом: раз он новый, то и уязвимости там точно должны быть.
Формулы
Когда вы хотите добавить формулу на Хабр, то вам предлагают сделать всё через красивый интерфейс. Вы вбиваете LaTeX, Хабр рендерит формулу в картинку и вставляет её в текст.
Но если проанализировать запрос, то там просто передаётся json вида:
{
"type": "formula",
"attrs": {
"source": "1+1",
"src": "https://formula.jpg",
"inserted":false,
"width":131,
"height":17
}
}
Что превращается в картинку:
<img class="formula" source="1+1" alt="1+1"
src="https://formula.jpg" width="131" height="17">
Я решил попробовать вставить src="javascript:"
- по привычке вбиваю это везде и смотрю, где что меняется.
{
"type": "formula",
"attrs": {
"source": "Смотреть",
"src": "javascript:",
"inserted": false,
"width": 131,
"height": 17
}
}
Результат:
<img class="formula" source="Смотреть"
alt="Смотреть" src="javascript:" width="131" height="17">
Значение src
не проверяется ни на что.
В этом есть ряд проблем:
Можно указать там ссылку на свой сервер и отдавать картинку, при этом собирая статистику. Поэтому разрешать на ресурсе внешние источники для картинок - уменьшение безопасности юзеров. Ну ладно, чо.
Можно через картинку делать любой
GET
запрос, и если кто-то из разработчиков перепутает "безопасные" (GET, HEAD) и "небезопасные" (POST, PUT, PATCH, ...) типы запросов, то может быть плохоЛюбая ссылка может быть, ну, любой. И если для
<a href="...">
это может быть проблемой (например, кто-то вставит<a href="javascript:alert('xss')">
и превратит ссылку в xss), то для картинок это в принципе безопасно, потому что браузеры не выполняют javascript код в картинке.<img src="javascript:alert('xss')">
можете вставлять на все свои сайты прямо сейчас, всё будет ок. Даже если вы сделаете<img src="evil.svg">
, а внутриevil.svg
будет скрипт, то он не выполнится. Браузеры умные. Но вот какая проблема: если не работаетjavascript:
и всякие злые картинки, то можно попробовать "вырваться" из текущего контекста. И вот тут нефильтрованные данные очень помогают.
Как вырваться из контекста
Если html выглядит как
<img src="{url}">
то если передать
url = "> <script>alert('xss')</script> <img src="
то это всё развернётся в
<img src=""> <script>alert('xss')</script> <img src="">
что позволит вставить произвольный скрипт в код страницы.
Поэтому когда я вижу, что мой пользовательский ввод попадает куда-то на html-страницу, я сразу туда ставлю <
, >
, <sc<script>ript>
и прочие прикольные конструкции.
И хотя я так делаю, идея эта достаточно тупая - по двум причинам:
Шаблонизаторы по умолчанию безопасные, то есть если даже вы напишете
this is {user_input}
, то нормальный шаблонизатор (неважно какой - в django это django templates или jinja, например, но принцип везде один и тот же) заэкранирует спец символы и заменит всякие<
на<
, а"
на"
Есть всякие bleach итд, которые умеют "очищать" html код от всякой дичи. Тут две важные вещи: 1) работает очищение как правило на основе белых списков (т.е. списка того, что разрешено), так что любой не разрешённый тег/атрибут будет удалён; шанс, что разрабы разрешили какой-то опасные тег, стремится к нулю 2) публичные библиотеки тестируются кучей разрабов, и вероятность, что именно вы тот самый супергерой, который нашёл баг в годами оттестированной библиотеке, тоже стремится к нулю.
Тем не менее, я не теряю надежды и пробую:
{
"type": "formula",
"attrs": {
"source": "Смотреть",
"src": "javascript:\"",
"inserted": false,
"width": 131,
"height": 17
}
}
Видите эту дополнительную "
после javascript:
? Я её заэкранировал, чтобы она была частью атрибута src
. Если бы она работала, она позволила бы закрыть атрибут src
и добавить свой html.
Но результат ожидаемый, я вас предупреждал - "
превращается в "
:
<img class="formula" source="Смотреть" alt="Смотреть"
src="javascript:"" width="131" height="17">
И кавычка становится частью текста:
Но тут есть один нюанс, и называется он "чужими руками". Я так называю подход, когда не вы напрямую вставляете плохие данные, а какой-то доверенный код это делает за вас.
Смотрите: разрабы никогда не доверяют пользовательскому вводу. У вас просто нет шансов. С другой стороны, разрабы доверяют своим библиотекам. И если вы заставите библиотеку вставить ваш код, то проверяться это не будет.
Чужими руками в данном случае у нас будет библиотека для т.н. lightbox - то есть отображения картиночек в полный экран с уютным затемнением. Потому что при нажатии на несуществующую картинку эта библиотека включает серый фон, берёт содержимое тега src
и вставляет его в ссылку, типа "Вот эту картинку я не могу прогрузить: {src}". Этот кусок html целиком создаётся этой lightbox библиотекой прям на лету, там нет никаких валидаций от разработчиков Хабра. Поэтому моя строка javascript:"
вставляется туда как есть, и я успешно закрываю атрибут href
своей кавычкой:
Можно ли выбраться из тега <a>
? Это же технический ресурс, конечно же да! Просто вставляем javascript:</a>Some text
:
{
"type": "formula",
"attrs": {
"source": "Смотреть",
"src": "javascript:\"></a>Some text",
"inserted":false,
"width":131,
"height":17
}
}
Мы вырвались!
Что делать, когда вырвался
Экспериментировать!
Во-первых, внимательный читатель заметил, что хотя Хабр вставляет формулу как картинку, но lightbox вставляет ссылку, и туда можно запихнуть просто
javacript:alert('xss')
, и при нажатии на ссылку будет выполняться скрипт, да?
Не совсем:
Заставлять пользователя ещё раз кликать, причём по надписи "эта картинка не может быть загружена", будет как-то странно
Даже если ссылка и ведёт на
javascript:
, скрипт всё равно не выполнится, т.к. стоит атрибутtarget="_blank"
Во-вторых, мы могли бы вставить классический
<script>alert('xss')</script>
, да?
Нет, потому что этот кусок html создаётся динамически, и скрипт автоматически выполняться не будет.Тогда пробуем что-нибудь более извращённое, например, вставить
<div onmouseover=alert('xss')>xss</div>
. По идее, если юзер проведёт мышкой над этой надписью, то сработает скрипт.
{
"type": "formula",
"attrs": {
"source": "Смотреть",
"src": "javascript:\"></a>Some text
<div onmouseover=alert('xss')>xss</div><!--",
"inserted":false,
"width":131,
"height":17
}
}
Кстати, в конце я добавил <!--
, чтобы весь последующий html закомментировался и не показывалось сообщение "Эта картинка не может быть загружена". Это похоже на sql injection, где используют --
, чтобы закомментировать оригинальный sql запрос.
Облом! Оказывается, там есть "poor man's escaping" - пробелы превращаются в %20
. А мне они нужны для задания атрибута onmouseover
.
Я уже думал, что тут наши полномочия как бы всё, но я решил погуглить какой-нибудь нечитаемый RFC, чтобы узнать, какие разделители атрибутов существуют.
Никогда не догадаетесь.
Спасибо тебе, чувак! Короче, можно написать <div/onmouseover=alert('xss')>
. Да, /
- это разделитель. Потому что могут.
{
"type": "formula",
"attrs": {
"source": "Смотреть",
"src": "javascript:\"></a>Some text
<div/onmouseover=alert('xss')>xss</div><!--",
"inserted": false,
"width": 131,
"height": 17
}
}
Ну вот, теперь жить можно. Осталось оформить это для пользователей, ui/ux, вот это всё. Я расширил <div>
на весь экран (css, гори в аду!), добавил кота на фоновую картинку (сыграло на руку то, что внутри style=...
можно не писать пробелы), текст поправил, и вышло вот что:
{
"type": "formula",
"attrs": {
"source": "Смотреть",
"src": "javascript:\"></a>
<div/onmouseover=alert('xss')>
<div/style=\"position:fixed;bottom:0;width:100%;height:100vh;background-image:url('https://kot-i-koshka.ru/wp-content/uploads/2017/11/zloj_kot_agressivnyj_prichiny_1510142737_5a02f3116e9cb.jpg');background-position:center;background-repeat:no-repeat;background-size:cover;\"> </div>
</div><!--<a",
"inserted": false,
"width": 131,
"height": 17
}
}
Выводы
А нет никаких выводов. Вы сами всё знаете. Я зол, как кот на картинке, и чувство вины гложет меня изнутри. Стараюсь вот развлекаться. Жаль, что взломы сайтиков ни на что не влияют.
Оставайтесь людьми и делайте всё, что в ваших силах, даже если вам кажется это незначительным.
Подписывайтесь на мой канал "Блог погромиста", там скоро будет новый онлайн-квест.