
Всегда хотел взломать Хабр. Мечта такая, но как-то руки не доходили. И вот, вдохновившись статьей о праведном взломе через 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. Если бы она работала, она позволила бы закрыть атрибут scr и добавить свой 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
	}
}Выводы
А нет никаких выводов. Вы сами всё знаете. Я зол, как кот на картинке, и чувство вины гложет меня изнутри. Стараюсь вот развлекаться. Жаль, что взломы сайтиков ни на что не влияют.
Оставайтесь людьми и делайте всё, что в ваших силах, даже если вам кажется это незначительным.
Подписывайтесь на мой канал "Блог погромиста", там скоро будет новый онлайн-квест.
Комментарии (16)
 - kamisatoayaka17.03.2022 14:45+39- Веб-разработчики пишут небезопасный код по умолчанию - Да что тут говорить, если оно всё тормозит невероятно. Крутится и крутится по полминуты после каждого клика, JavaScript, видите ли, рисует. Просто отдать с сервера html, отображаемый чуть ли не со скоростью моргания человеческого глаза нельзя, это фу устаревшее. Дорогие разработчики, слезьте на день со своих топовых маков и откройте своё чудо на планшете/ноутбуке 2015 или 2012 года. 
 - breslavsky17.03.2022 17:41- Отличная статья! У меня тоже был разбор ошибки в API хабра тут https://habr.com/ru/post/647957/ 
 - icecube09218.03.2022 00:57- На любом сайте можно найти XSS, стоит только захотеть. Это практически неубиваемая уязвимость. 
 
           
 








ErhoSen
Интересно, когда хабр уже "закопает стюардессу", и добавит простой markdown редактор.
Hrodvitnir
Да, вариант с MD мне нравился гораздо больше. Сидишь себе пишешь статью в VS Code с MD плагином и пьешь кофеек, а не вот это вот все.
staticmain
Да если бы их визивиг хоть как-то адекватно работал, а не делал отдельный запрос на сервер на каждый абзац со слипом в секунду между запросами…
Amareis
А лучше AsciiDoc