Что такое xss уязвимость
xss уязвимость
Угрозы xss атак с применением javascript
Прежде чем начать, стоит оговориться, что данный материал несет исключительно информационный характер.
Что такое xss атака?
Это такой тип атак, который внедряет в веб-системы вредоносный код, заставляя её выдавать измененные данные, подменяет ссылки (видимые/скрытые) или выводит собственную рекламу на пораженном ресурсе.
Существует два направления атак:
Пассивные – которые требуют непосредственного вмешательства субъекта атаки. Суть заключается в том, чтобы заставить жертву перейти по вредоносной ссылке для выполнения «вредокода». Такой тип атак более сложный в реализации, ведь необходимо обладать не только техническими, но и психологическими знаниями.
Активные – это вид атак, когда хакер пытается найти уязвимость в фильтре сайта. Как же реализуется такая атака? Все очень просто. Нужно при помощи комбинации тегов и символов создать такой запрос, чтобы сайт его понял и выполнил команду. Как только дыра в безопасности найдена, в наш запрос можно вложить «вредокод», который, к примеру, будет воровать cookie и пересылать в удобное нам место. Приведем пример скрипта, ворующего “печеньки” с сайта:
Обычно приходится серьезно потрудиться, чтобы найти дыру в безопасности сайта, ведь большинство фильтров достаточно устойчивы. Но их пишут люди, а им свойственно ошибаться.
Правила безопасности
Откуда и почему вообще возникают подобные уязвимости, которые приводят к катастрофическим последствиям? Все дело во внимательности и знании людей. Разработчики должны писать правильный код, поэтому в этом разделе мы расскажем про минимальные правила безопасности написания сайтов.
Как применяется атака, мы уже рассказывали, но повторимся еще раз. Вся суть xss атаки — это обнаружение дыры в фильтре с целью его обхода.
1. Одно из самых первых и основных правил для разработчика – это применение любого (хотя-бы самого минимального) фильтра.
В проведенном нами исследовании сайтов почти все они были защищены, но все же находились и те, которые не использовали никакой фильтрации получаемых данных. В основном, это встречается на сайтах, написанных на языке PHP. Но, например, в фраемворках python, таких как: flask или Django уже есть встроенные минимальные фильтры, их остается только усилить.
2. Фильтрация символов и вложенных конструкций.
Минимальный фильтр защитит нас от любительских атак и неграмотных специалистов, но от серьезных хакеров нужно строить более серьезную защиту, с более детальной фильтрацией данных. Разработчики должны учитывать и понимать возможную реализацию xss атаки и строить фильтр таким образом, чтобы он распознавал вложенные конструкции. Например, хакер может создать многоуровневую конструкцию, и в самый нижний из них вложить вредоносный javascript код. Фильтр будет блокировать верхний уровень, но нижний будет выполняться.
3. Фильтр должен учитывать всевозможные комбинации символов.
Одной из наших любимых проверок на xss уязвимость является использование открытых и закрытых скобок.
Например: “/?,#”>>>><
XSS глазами злоумышленника
Что такое XSS и как от него защитится все уже давно знают, поэтому буду краток. XSS это возможность злоумышленника определенным образом (ссылку на возможные варианты смотрите в конце статьи) интегрировать в страницу сайта-жертвы скрипт, который будет выполнен при ее посещении.
Интересно, что в большинстве случаев, где описывается данная уязвимость, нас пугают следующим кодом:
Как-то не очень страшно 🙂 Чем же действительно может быть опасной данная уязвимость?
Пассивная и активная
Существует два типа XSS уязвимостей — пассивная и активная.
Активная уязвимость более опасна, поскольку злоумышленнику нет необходимости заманивать жертву по специальной ссылке, ему достаточно внедрить код в базу или какой-нибудь файл на сервере. Таким образом, все посетители сайта автоматически становятся жертвами. Он может быть интегрирован, например, с помощью внедрения SQL-кода (SQL Injection). Поэтому, не стоит доверять данным, хранящимся в БД, даже если при вставке они были обработаны.
Пример пассивной уязвимости можно посмотреть в самом начале статьи. Тут уже нужна социальная инженерия, например, важное письмо от администрации сайта с просьбой проверить настройки своего аккаунта, после восстановления с бэкапа. Соответственно, нужно знать адрес жертвы или просто устроить спам-рассылку или разместить пост на каком-нибудь форуме, да еще и не факт что жертвы окажутся наивными и перейдут по вашей ссылке.
Причем пассивной уязвимости могут быть подвержены как POST так и GET-параметры. С POST-параметрами, понятно, придется идти на ухищрения. Например, переадресация с сайта злоумышленника.
Следовательно, GET-уязвимость чуть более опасна, т.к. жертве легче заметить неправильный домен, чем дополнительный параметр (хотя url можно вообще закодировать).
Кража Cookies
Это наиболее часто приводимый пример XSS-атаки. В Cookies сайты иногда хранят какую-нибудь ценную информацию (иногда даже логин и пароль (или его хэш) пользователя), но самой опасной является кража активной сессии, поэтому не забываем нажимать ссылку «Выход» на сайтах, даже если это домашний компьютер. К счастью, на большинстве ресурсов время жизни сессии ограничено.
Что такое XSS-уязвимость и как тестировщику не пропустить ее
По моему наблюдению довольно много тестировщиков когда-либо слышали такое понятие, как XSS-уязвимость. Но мало кто может просто и на пальцах рассказать на собеседовании про нее. Или эффективно проверить веб-сайт на наличие этой уязвимости. Давайте вместе разберемся со всем этим подробнее и попробуем сами найти несложную XSS-уязвимость на демо-странице, которую я специально подготовил к этой статье.
Если вы гуру тестирования безопасности и на раз-два участвуете в баунти-программах крупных IT-компаний, а количество найденных вами XSS исчисляется десятками или даже сотнями — можно смело проходить мимо этой статьи. Если же вы новичок в теме и только начинаете интересоваться поиском уязвимостей — добро пожаловать под кат.
Определение
XSS (англ. Cross-Site Scripting — «межсайтовый скриптинг») — довольно распространенная уязвимость, которую можно обнаружить на множестве веб-приложений. Ее суть довольно проста, злоумышленнику удается внедрить на страницу JavaScript-код, который не был предусмотрен разработчиками. Этот код будет выполняться каждый раз, когда жертвы (обычные пользователи) будут заходить на страницу приложения, куда этот код был добавлен. А дальше существует несколько сценариев развития.
Первый: злоумышленнику удастся заполучить авторизационные данные пользователя и войти в его аккаунт.
Второй: злоумышленник может незаметно для жертвы перенаправить его на другую страницу-клон. Эта страница может выглядеть совершенно идентично той, на которой пользователь рассчитывал оказаться. Но вот принадлежать она будет злоумышленнику. Если пользователь не заметит подмены и на этой странице введет какие-то sensitive data, то есть личные данные, они окажутся у злоумышленника.
Третий… да в общем-то много чего еще можно придумать. Почти все, что может JavaScript, становится доступным для злоумышленника. Чуть ниже мы рассмотрим подробнее один из таких примеров. А пока давайте попробуем чуть подробнее обсудить, как именно устроена уязвимость. И почему злоумышленнику удается внедрить свой код в чужое приложение без доступа к его исходникам.
Небольшое предупреждение. Вся информация далее представлена исключительно в информационных целях. Тестировщик должен уметь проверять свое веб-приложение на уязвимости. Однако, использование XSS-уязвимостей на чужих ресурсах является незаконным.
Если говорить про действующее российское законодательство, когда исследователь тестирует чужой продукт на предмет уязвимостей или проникает в чужую сеть без ведома и согласия владельца, его действия могут быть расценены как неправомерные.
Как устроена уязвимость?
Прежде всего, как именно удается внедрить на страницу JavaScript-код, которого там раньше не было? И как получается распространить этот код среди других пользователей?
Например, можно добавить JavaScript-код в поле ввода, текст из которого сохраняется и в дальнейшем отображается на странице для всех пользователей. Это может быть поле для ввода информации о себе на странице профиля социальной сети или комментарии на форуме.
Злоумышленник вводит текст (и за одно вредоносный код), который сохраняется на странице. Когда другие пользователи зайдут на эту же страницу, вместе с текстом они загрузят и JavaScript-код злоумышленника. Именно в момент загрузки этот код отработает. Конечно, уязвимость сработает, только если текст при сохранении не будет обезопасен. О том, как это сделать, и почему разработчики иногда забывают об этом, поговорим чуть позже.
Это лишь самый простой и очевидный пример того, где может быть спрятана уязвимость. Более интересный пример мы с вами чуть ниже рассмотрим на специально подготовленной демо-страничке.
А пока давайте двигаться дальше.
Почему такие ошибки часто встречаются на веб-проектах?
Суть в том, что браузер не может самостоятельно отличить обычный текст от текста, который является CSS, HTML или JavaScript-кодом. Он будет пытаться обрабатывать все, что находится между тегами
В случае, если страница является уязвимой, после ввода этого кода на странице появится вот такое окошко:
Оно и будет означать, что наш JavaScript-код исполнился и мы нашли XSS-уязвимость.
Итак, вводим код и видим следующее предупреждение:
Форма не позволяет нам осуществить поиск по такому значению, так как форма валидируется и хочет работать только с буквами и цифрами. На первый взгляд кажется, что разработчик все учел и защитил страницу от XSS, но это не совсем так.
Помните, чуть выше мы с вами заметили, что текст, который мы вводим в поле поиска, отображается в URL в так называемом GET-параметре? Имя этого параметра “q”, а значение — то, что мы вводим в поле поиска. Это сделано для того, чтобы можно было скопировать URL вместе с этой самой строкой поиска и в следующий раз открыть страницу сразу с нужными авторами.
Например, вот такой URL откроет страницу сразу только с книгами Рея Брэдбери: playground.learnqa.ru/demo/xss?q=Рей+Брэдбери
В отличие от формы, валидацию URL разработчик сделать не мог — любой пользователь в своем браузере может ввести любой URL, какой захочет, в том числе и с любым значением GET-параметра. Задача разработчика в этом случае — не забыть учесть все варианты и написать правильный обработчик значения этого GET-параметра.
Проверим, не забыл ли наш разработчик все учесть тут. Попробуем в GET-параметр “q” подставить тот самый JavaScript-код: https://playground.learnqa.ru/demo/xss?q=
Перейдя по этому URL мы видим, что на странице появилось окошко со значением 123. Но почему?
Все довольно просто. Помните, когда сайт не может найти нужные книги по заданному поисковому запросу, он текст этого поискового запроса выводит в тексте ошибке? Мол, не нашли ничего по запросу “бла-бла”. Вот вместо этого “бла-бла” у нас теперь JavaScript-код с алертом. Разработчик написал валидацию поля ввода и решил, что так он защитил сайт от того, чтобы JavaScript мог оказаться в поисковом запросе. И не стал экранировать текст ошибки. Нам же удалось обойти валидацию через URL, поменяв там значение поискового запроса.
Ради интереса теперь можем вывести значение нашей сессионной cookie, для этого вместо в URL надо подставить другой код:
С этим я уже предоставлю поиграться вам самостоятельно. 🙂
Найдя ошибку, стоит обратиться к разработчикам — они ее исправят.
Способов закрыть ошибку довольно много. Экранировать текст — не единственный из них. Еще можно запретить самому JavaScript видеть некоторые cookie. Для этого у cookie есть специальный параметр “http only”. Если он выставлен в TRUE, JavaScript никак не сможет узнать, что такая cookie вообще выставлена и не сможет ее прочитать и передать злоумышленнику даже в том случае, если ему удастся найти XSS на вашем проекте.
Все это — лишь малый, далеко не полный список манипуляций, предотвращающий XSS-уязвимости. Как писалось выше — при обнаружении XSS в ходе тестирования лучше всего пообщаться с программистами.
Если Вам интересно знать больше про тестирование безопасности, хочется лучше разобраться в устройстве клиент-серверной архитектуры, понять и отточить самые эффективные способы поиска уязвимостей на настоящем веб-приложении, приходите на мой курс “Тестирование безопасности”. Вся необходима информация есть в моем профиле.
Вас ждет только полезная и нужная теория без воды и большое количество практических примеров и заданий. Вы будете исследовать множество веб-страниц, напичканных самыми разными уязвимостями. Итоговой работой станет большое исследование либо вашего рабочего проекта, либо одного из веб-приложений таких гигантов как Google, Facebook, Twitter и так далее.
Эффективный поиск XSS-уязвимостей
Про XSS-уязвимости известно давным-давно — казалось бы, нужен ли миру ещё один материал о них? Но когда Иван Румак, занимающийся тестированием безопасности, поделился методологией их поиска на нашей конференции Heisenbug, реакция зрителей оказалась очень положительной.
И спустя два года у этого доклада по-прежнему растут просмотры и лайки, это один из самых востребованных материалов Heisenbug. Поэтому теперь мы решили, что многим будет полезна текстовая версия, и сделали ее для Хабра.
Под катом — и текст, и видео. Далее повествование идет от лица Ивана.
Обо мне
Я занимаюсь тестированием безопасности. По сути, занимаюсь всеми вопросами, связанными с безопасностью сайтов. Параллельно участвую в разных Bug Bounty, занимаю 110 место на платформе HackerOne, нахожу баги в Mail.ru, Яндексе, Google, Yahoo! и других крупных компаниях. Обучаю, консультирую, рассказываю про безопасность в вебе и не только.
История доклада
Когда я начал интересоваться безопасностью, то был тестировщиком и проверял функциональные баги, а не те, что связаны с безопасностью. Но я увлекся безопасностью и однажды стал самым прошаренным тестировщиком в этой сфере. Ко мне начали приходить другие тестировщики и разработчики. Я понял, что тестировщики тоже хотят научиться искать уязвимости, им это интересно, но при этом они не знают, что конкретно нужно делать.
Что такое XSS? Как искать? Как понимать, есть XSS или нет? Сейчас разберемся.
Что такое XSS-уязвимости
Методология поиска XSS (которой пользуюсь сам и с помощью которой нашел более 60 XSS в Bug Bounty за последний год)
Какую проверочную строку (пейлоад) использовать для поиска XSS-уязвимостей
Кейсы из разных Bug Bounty-программ (какие XSS были, как их можно найти, и баги, которые по методологии поиска похожи на поиск XSS)
Зачем искать уязвимости?
Вам — полезный навык, который никогда не будет лишним. Компании, где вы работаете — дополнительная безопасность. Win-win!
Что такое XSS
XSS (Cross-Site Scripting) — возможность выполнения произвольного JavaScript-кода в браузере жертвы в контексте вашего сайта.
Вспомним, как вызывается JavaScript из HTML:
— всё, что внутри, будет срендерено браузером как JavaScript.
click to trigger javascript — если гиперссылка ведет не на схему HTTP/HTTPS, а начинается со схемы JavaScript, то при нажатии на ссылку всё, что после схемы JavaScript, будет срендерено как JavaScript.
— то же самое, что и с гиперссылкой, только ничего не надо кликать, сработает при прогрузке.
XSS — одна из самых распространенных уязвимостей в вебе. К XSS уязвимо более 95% веб-приложений. Чтобы найти баг, не обязательно обладать специальными навыками, проходить курсы или получать высшее образование.
И действительно, несмотря на то, что XSS — распространенная уязвимость, она остается одной из самых серьезных клиентских уязвимостей.
Причины возникновения XSS
Во-первых, XSS возникает при генерации HTML-страницы, когда разработчику нужно поместить туда указанные пользователем данные (ФИО, организация). Если разработчик записал данные в БД, затем тянет ее в HTML-шаблон, то это stored (сохраненный) XSS.
Разработчику могут понадобиться параметры из URL или тела запроса. Такой тип XSS называется reflected.
Причин XSS куча, потому что есть динамические изменения страницы с помощью JS, есть события, которые постоянно происходят на клиентской стороне с JS.
Но в этом докладе я расскажу про самые распространенные типы — stored XSS и reflected XSS.
Возьмем пример — обычная страница ВКонтакте. О чем подумает человек, который хочет найти XSS-уязвимости?
Во-первых, он обратит внимание на то, что есть поля, можно куда-то зарегистрироваться и что-то ввести.
Что произойдет в этом случае?
Мы, как пользователь, который хочет зарегистрироваться во ВКонтакте, заливаем ему наши проверочные строки. Дальше разработчик сохраняет их в базу данных, и с этими данными ему надо работать. Нужно показывать их пользователю на его странице, в личных сообщениях и много где еще. Дальше данные попадают пользователю в браузер, когда они возвращаются ему обратно.
Допустим, разработчик не подумал, что в качестве имени пользователя могут быть не только честные данные, а еще и HTML-теги, которые встраиваются в оригинальный HTML-шаблон. Браузеру пофиг, он рендерит все, что ему сказал разработчик, поэтому рендерится и строка.
Оно могло бы выстрелить где-нибудь здесь:
Конечно, во ВКонтакте такой уязвимости нет. Но, так как эта страница является публично доступной, любой в интернете может на нее зайти, то это была бы довольно серьезная уязвимость.
Но вообще мы, как тестировщики, которые ищут XSS-уязвимости, чаще всего делаем это блэкбоксом. Мы не знаем, что происходит на сервере, какая база данных там используется, делает ли разработчик что-то с этими данными. Всё, что у нас есть, — это поле, куда мы можем что-то ввести, и какие-то страницы, куда это потом возвращается.
Методология поиска XSS, которую я сейчас вам покажу, основана как раз на том, что мы не знаем, какие процессы происходят на сервере.
XSS-методология
Помещаем пейлоад (проверочную строку, призванную выявлять уязвимости) во все поля и параметры.
Смотрим в DOM на предмет санитизации.
Рано или поздно спецсимволы не перекодируются, или выполнится функция alert.
Раскручиваем дальше или репортим, как есть.
Еще один пример — страница поиска. В поле поиска попробуем ввести «qweqwe».
Мы видим, что строка «qweqwe» попала из поля для поисков в параметр query. И она попала в страницу 17 раз. То есть у нас есть 17 потенциальных мест, где разработчик может не подумать о выводе этой строки пользователю в браузер, и может возникнуть XSS-уязвимость.
Конечно, «qweqwe» недостаточно, чтобы выявить XSS-уязвимость, мы добавим туда спецсимволы:
Это называется HTML entities. Особенность использования браузером этой кодировки заключается в том, что браузер рисует соответствующий символ на странице, но HTML-теги, состоящие из этих символов, не рендерятся браузером как код:
Это выглядит вот так:
Слева у нас HTML-код, который должен отрендерить браузер, но он просто показывает его как строку.
Санитизация — преобразование определенных символов пользовательской строки в соответствующие HTML entities или другую кодировку.
Другими словами, у нас есть набор потенциально опасных символов, которые мы хотим санитизировать. Мы хотим их превратить в HTML entities, чтобы они не встраивались в наш изначальный шаблон, который мы хотим исполнять на пользователя, и нельзя было протолкнуть чужой JS.
Вернемся к этому примеру. Двойная кавычка заинкодилась в ", получается, санитизация есть.
А если бы не было? Мы попробуем ввести ‘ “ test, и поищем по строке «qweqwe»:
Input: qweqwe ‘ » test
Мы увидим, что test начал подсвечиваться коричневым. Браузеры помогают нам: они подсвечивают атрибуты, значения атрибутов и названия тегов разными цветами. Атрибуты всегда коричневые, имена тегов — розовые, значения параметров — синие.
Если бы вся строка была синяя, мы могли бы сразу понять, что она попала внутрь значения атрибута, и можно было бы сделать вывод, что XSS нет.
Но здесь предположим, что она есть, и у нас записался атрибут test. И если мы вместо этого атрибута используем обработчик событий, например:
Input: qweqwe ‘ » onfocus=’alert()’ autofocus
Получаем reflected XSS:
Это сложно, поэтому я предложу решение в виде универсального пейлоада — это строка, которая должна выявлять XSS в разных контекстах и которая не требует дополнительного раскручивания в таких местах.
XSS – Level 0
Начнем с самой простой строки, на которую натыкались все, кто когда-то интересовался тестированием безопасности:
Посмотрим на примере языка PHP, когда я как разработчик хочу выводить пользователю HTML-код и подтягиваю туда значение параметра, в нашем случае — name:
Функция echo() в PHP не делает санитизацию, она выводит всё как есть. То есть это типичная reflected XSS-уязвимость. И если мы поместим в параметр name на этой странице наш текущий пейлоад, он срендерится браузером, потому что никакой санитизации нет. Он встраивается как есть, браузер не отличает пользовательскую строку от оригинальной и рендерит.
То же самое, если разработчик не берет параметр из URL, а берет из базы данных данные, которые когда-то вводил пользователь:
Вот пример посложнее:
Что, если мне надо брать значение параметра и отображать его внутри значения атрибута? Как меня могут хакнуть в этом случае?
И раз мы знаем, что есть кейсы, когда мы можем попасть внутрь значения атрибута, почему бы нам сразу не добавить «> в пейлоад?
XSS — Level 1
Это не сработает, потому что тег нужен браузеру, чтобы отображать имя текущей вкладки. Браузер думает, что раз это всего лишь название вкладки, ему незачем рендерить значение этого тега, и он просто будет рендерить всё как текст.
Вроде звучит здорово, но если мы попали внутрь тега
Разработчик написал JavaScript, внедрил его у себя на страницу, но какую-то переменную берет из пользовательского значения. Поместим туда наш текущий пейлоад:
Здесь все тоже достаточно тривиально, просто закрываем тег разработчика
Я не буду дальше мучить вас каждым таким тегом.
, значения которых рендерятся браузером как строка.
XSS — Level 2
А мог ли разработчик обособлять все одинарными кавычками? Браузер это принимает, это вполне нормальное поведение. И если бы мы поместили туда наш текущий пейлоад, разумеется, он бы не сработал: он бы не обнаружил эту XSS-уязвимость, потому что мы закрываем двойную кавычку.
Здесь тоже все просто: достаточно добавить одинарную кавычку перед двойной, и мы закроем и этот кейс.
Есть еще случай, когда разработчику надо подставлять параметры внутрь гиперссылки:
Цель — редиректнуть пользователя туда, откуда он пришел. Например, пользователь пришел в приложение. Оно редиректит его на аутентификационный поддомен, и когда он аутентифицировался, этот поддомен должен редиректнуть его обратно в приложение.
Можно вызывать JS в гиперссылках, в том числе при редиректах, если использовать схему JS:
Если поставить перед javascript пробел, то это тоже сработает:
Сработает не только %20 (пробел), но и %09 (табуляция).
Я покажу в качестве примера XSS, который я нашел на поддомене Mail.ru, — biz.mail.ru.
Сценарий атаки: я бы просто скинул ссылку на эту ошибку 500 другому пользователю. И если бы он нажал кнопку «Обновить», у него бы сработал JS, который я захотел.
Подумаем, как разработчик вообще мог починить такую уязвимость. В случае с XSS мы можем просто санитизировать пользовательские специальные символы. Но в случае со схемой JavaScript это не сработает, потому что здесь немного другие символы.
Используя синтаксис JavaScript, можно сделать пейлоад, который выглядит как URL, но также вызывает функцию alert() при нажатии:
А это сработает, если запретить слово javascript в URL?
Как нам уже известно про HTML entity, браузер в некоторых местах использует эту кодировку неоднозначно. Если слово javascript: заэнкожено в HTML entity, при нажатии на эту ссылку браузер всё равно поймет, что HTML entity — это схема JavaScript, её тоже можно использовать.
Таким образом мы обойдем защиту, если бы она была.
Единственный правильный способ здесь — это требовать, чтобы ссылка начиналась на http(s) или была относительной.
XSS — Level 3
Наш пейлоад довольно большой и попадает в несколько кейсов:
Мне не нравится, что мы вроде делаем крутой пейлоад, а используем — самую нубскую вещь, которую можно найти в начале поиска XSS-уязвимостей.
Я предлагаю использовать iframe с обработчиком событий:
— тег для отображения страницы внутри страницы. Допустим, вы находитесь на каком-нибудь сайте и, если сайт хочет подгрузить в себя еще один сайт, то разработчик использует этот тег. Также там есть атрибут src — адрес до сайта, который он хочет показать у себя. И независимо от того, прогрузился путь или нет, onload будет работать всегда.
Плюсы iframe:
Легко заметить, если пейлоад встраивается в страницу, но на onload работают санитайзеры.
Есть волшебный аттрибут srcdoc :
Разработчики используют iframe на всяких форумах, дают возможность пользователю помещать его на сайт, но если они не подумали про этот атрибут, то возможно выполнение произвольного JS.
Здесь в качестве значения атрибута srcdoc используется просто HTML entity:
Если у вас не получилось раскрутить XSS, например, разработчик решил, что хочет разрешить встраивать пользовательский iframe, но без обработчиков событий и без srcdoc, то у нас всё равно есть уязвимость другого типа — open redirect, пейлоад которого выглядит так:
У нас есть iframe, его src указан на другую страницу в интернете, подконтрольную злоумышленнику. Содержимое этой страницы довольно простое — всего лишь скрипт, который задает top.window.location на другую страницу.
И если браузер срендерит это на каком-то сайте, произойдет редирект на https://evil.com.
У браузера есть иерархия окон, есть окно верхнего уровня и промежуточные окна. И iframe, который подгружается внутри сайта, является промежуточным окном, но при этом он может влиять на окно верхнего уровня. Он может переписать его top.window.location, и возникает уязвимость open redirect.
Лечится это атрибутом sandbox, но никто об этом не задумывается. Если есть разрешение устраивать пользовательский iframe, то таким пейлоадом можно редиректить других пользователей куда угодно.
XSS — Level 1337
Перейдем на суперхакерский уровень XSS-уязвимостей:
Таким образом, исходя из этих двух фактов, мы можем прийти от такого пейлоада:
Покажу еще один пример из Bug Bounty, он из приватной программы. Это XSS-уязвимость в личных сообщениях. Разработчики обрезали всё, что подходит под паттерн » «, чтобы защититься от XSS-уязвимостей. Если пошлем закрытый тег, он обрежется:
Однако, если мы пошлем незакрытый тег, то он отрендерится:
Фреймворки
Также есть разные фреймворки, например, клиентские AngularJS и VueJS.
Здесь тоже есть специфический пейлоад:
Если это посчиталось на клиентской стороне и превратилось в 49, то здесь тоже возможна XSS-уязвимость. Нужно использовать constructor.constructor и вызвать alert:
Пейлоад, конечно, зависит от версии AngularJS, поэтому нужно чекнуть версию и подобрать пейлоад из списка.
Как и в случае с прошлыми примерами HTML entity, для AngularJS не имеет значения, используются ли фигурные скобки или HTML entities. Если разработчик подумал: «Я использую AngularJS или VueJS и не хочу, чтобы мне вставляли фигурные скобки, буду их обрезать», то достаточно поместить HTML entity-представление, и браузер уже срендерит это как надо.
У VueJS пейлоад тот же самый.
Вот такой пейлоад получился:
‘»/test/ — если можем записать свой атрибут (onerror, onmouseover, …)
Но ведь есть много полиглот-пейлоадов. В чем отличия?
размер строки меньше, т. к. не предусматривает попадания туда, где нужно использовать схему JavaScript (ссылки);
включает проверку AngularJS, VueJS;
расширенное покрытие случаев с записью атрибутов.
Обычные полиглот-пейлоады используют onload=’alert()’, но у многих элементов нет такого события. В моем это:
Что это нам даст? JS-то вызовем, потому что мы знаем, что нам достаточно этого для вызова JS, но сразу мы этого не увидим. Поэтому я предлагаю добавлять такой код на каждую страницу, где вы ходите, через расширение:
Дальше идем в DOM и смотрим, в какой элемент мы попали.
Если мы выполним код, то получаем 1. Соответственно, если какой-то тег уязвим и мы записали туда свой атрибут, то можно дальше раскрутить это. Мы можем вместо test использовать onload или onmouseover, в зависимости от того, что поддерживает этот тег.
Можно внедрить этот код через расширение для браузера.
Почему XSS опасны?
У JS в браузере по умолчанию есть доступ к пользовательскому контенту:
DOM — это место, где JS может изменять HTML на стороне пользователя. JS может видеть через DOM всё, что вы видите у себя в интерфейсе в веб-приложениях.
Также у него есть доступ к LocalStorage и SessionStorage. Если разработчик хранит там сессионный ключ или другие данные для аутентификации, то JS легко может взять и потом послать его на другой вредоносный сервер.
Ни для кого не секрет, что куки — один из самых распространенных способов аутентификации в веб-приложениях. И у JS есть доступ к любым HTTP-ответам с этого же Origin. То есть, получив инъекцию в какой-то одной странице, мы можем вызвать любую другую страницу, на которой содержится чувствительная информация.
Например, сделаем запрос к странице b, где у нас есть API-ключи, и попросим вывести ответ:
Вместо страницы b могли быть данные для аутентификации или другие чувствительные данные.
JS может взаимодействовать с установленными программами и расширениями.
Безопасно ли, что у JS есть столько доступа ко всему?
Существует много вопросов, связанных с безопасностью. Разработчики браузера используют Origin для разграничения доступа JS между разными сайтами.
Origin = protocol + hostname + port
Также у браузеров есть Same-Origin-Policy (SOP) — фундаментальная защита, на которой основывается безопасность в вебе.
JS, выполняемый на одном Origin, не может получить доступ к содержимому другого Origin.
Можно сделать вывод, что XSS на одном Origin не опасен для другого Origin. У нас есть какой-нибудь поддомен, где можно выполнять пользовательский JS, потому что там не хостится ничего серьезного.
Но не всегда, потому что есть легальные обходы SOP. Разработчикам необходимо связываться с другими сайтами на клиенте, ходить на другие сайты, читать ответы, что-то туда посылать и что-то динамически обновлять. Они используют для этого CORS, WebSocket, PostMessage, JSONP, Flash.
Куки, поставленные на одном порту, можно прочитать на любом другом порту.
Есть экспериментальные фичи JS, которые иногда тоже несут угрозу для SOP. Иногда это приводит к неожиданным результатам.
Я приведу приватный пример из Bug Bounty — XSS в S3-бакете, позволяющий красть чужие файлы.
Контекст этого веб-приложения: это CRM, где можно заводить сделки с потенциальными клиентами и заполнять информацию о них, в том числе загружать файлы.
Я подумал: «Что будет, если загрузить HTML-страницу?»
Обычная страница, где я хочу посмотреть, на каком домене она выполняется. И она успешно загружается.
После открытия страницы сначала кажется, что XSS находится не в приложении, а в S3-бакете.
Они берут файл пользователя, кладут у себя в бакет, и дальше, когда пользователь хочет получить доступ к файлу, он делает это в приложении, но приложение редиректит его в S3-бакет, где хостится этот файл.
При попытке открыть файл на сервере генерируется подпись запроса, по которой файл будет доступен какое-то время. Нельзя просто так получить файл, надо, чтобы запрос был подписан. Это происходит на серверной стороне приложения, когда вы загрузили этот файл и хотите его открыть. На сервере происходит генерация подписи, и приложение редиректит пользователя на файл в S3-бакете, где эта подпись уже включена в запрос. По этой подписи файл будет доступен какое-то время.
Очевидно, что JS выполняется на другом Origin, потому что S3-бакет – поддомен Amazon, он не имеет никакого влияния на другой домен, на котором хостится основное приложение.
На первый взгляд, XSS бесполезны, но существуют следующие факторы:
возможность загрузить любой файл;
возможность выполнять произвольный JavaScript;
все файлы, в том числе других пользователей, кладутся в одну и ту же папку (например, в корень);
ссылкой на загруженный файл можно поделиться с кем угодно.
Эти факторы позволяют красть чужие файлы через перезапись ответа Service Worker. Для этого нужно создать и загрузить serviceworker.js:
Навешиваемся на событие fetch, переписываем ответ сервера на код iframe src и говорим, что тип ответа — text/html. Каждый запрос пользователя будет возвращать одинаковый ответ благодаря этому Service Worker.
Также загрузим exploit.html:
Это страница, которая нужна для эксплуатации этого Service Worker. Регистрируем его, указывая путь до него с подписями, потому что мы хостим этот serviceworker.js в этом же S3-бакете. И говорим, что скоуп — корень.
Что произойдет, если кто-то откроет exploit.html:
В браузере зарегистрируется Service Worker
При открытии любой страницы этого сайта, начиная от директории с serviceworker.js, ответ перепишется на:
От лица жертвы это будет выглядеть так:
Я пошлю фишинговое письмо с короткой ссылкой на свой exploit.html-файл. Зарегистрирую у пользователя Service Worker. Дальше, если этот пользователь захочет открыть файл у себя в организации, ответ перепишется на iframe, который ведет на мой сайт.
И я получу примерно такой запрос:
В Referer будет полный путь до файла, где пользователь захотел открыть свой файл. У меня есть полный путь с подписями, поэтому я могу открыть его, и это дает мне полный доступ к этому файлу в дальнейшем.
Бонус
Мало кто задумывается, что письма — это HTML-код. Например, есть такое письмо от Trello:
В письмо тоже подтягиваются пользовательские значения, возможно, там есть XSS. Но письма мы смотрим в почтовых клиентах (обычно Mail.ru, Яндекс) и, разумеется, XSS в письме ведет к XSS в почтовом клиенте, а это уже другой скоуп, который никак не влияет на организацию, которая послала это письмо.
Но здесь тоже есть XSS-уязвимость, она называется Email template’s HTML injection. Вместо вызова JS мы встраиваемся в HTML-шаблон, который прислала нам компания.
Пейлоад выглядит так:
Пример уязвимого шаблона:
На первый взгляд — обычное письмо. Но если посмотреть на тему, то можно увидеть, что там есть HTML-теги, гиперссылка и HTML-коммент.
Идея в том, что мы добавляем нашу вредоносную гиперссылку, комментим оригинальное письмо и отправляем это письмо кому угодно.
Так как мы можем контролировать гиперссылку и контекст, мы можем зафишить кого-то невнимательного, потому что ссылка будет вести на левый сайт.
Недавно мне пришло такое письмо:
Кто-то пытался эксплуатировать абсолютно то же самое: приглашали присоединиться к организации. Разумеется, это фишинг.
Я покажу еще один пример из Bug Bounty. Это был RCE (remote code execution) через инъекцию шаблона во FreeMarker.
Это приложение позволяет создавать свои события, свою кампанию отсылки имейлов.
Имейлы здесь кастомные. Предоставляем HTML, который нужно разослать. Уязвимость была уже в том, что можно послать любой HTML с официального адреса этой компании кому угодно.
Путем недолгих ковыряний я понял, что используется шаблон FreeMarker. А у шаблонизаторов FreeMarker есть такие штуки:
[#assign cmd = ‘freemarker.template.utility.Execute’?new()]
$
Достаточно вызвать, использовать его и передать ему в качестве параметра какую-то shell-команду, например, id.
И действительно показывает, что root:
Выводы
Если вы разрабатываете приложение, работаете над архитектурой приложения, то всегда надо иметь в виду, что интернет — это небезопасное место.
Нужно всегда помнить, что нельзя доверять пользовательскому вводу. Рассматривайте место, где пользователь вам что-то посылает, как потенциально вредоносное.
Нужно проверять свое приложение, потому что даже самый внимательный разработчик все равно когда-то ошибется, допустит у себя уязвимость, и проверка необходима — чем чаще, тем лучше.
Используйте универсальный пейлоад, помещайте его во все поля, в каждый input. Рано или поздно это сработает, потом уже научитесь раскручивать, успешно находить еще больше XSS, может быть, придумаете свои векторы атаки.
В любом приложении всегда есть уязвимости, в том числе XSS. Если вы ищете баги безопасности, вы не можете быть уверены, что их там нет. Возможно, вы просто не можете их найти, но они там есть. Используя такое убеждение, можно найти еще больше багов.
Это был доклад с одного из предыдущих Heisenbug, а мы тем временем активно готовим следующий: с 5 по 7 октября состоится онлайн-конференция Heisenbug 2021 Moscow. Там будут десятки новых докладов по тестированию, и описания нескольких из них уже можно прочитать на сайте.