Злоумышленники могут успешно атаковать 98% веб-приложений. И это не просто громкие цифры, а данные из исследования Positive Technologies. Как такое возможно, если есть инструменты и практики типа SAST, DAST и WAF, а разработчики вроде бы нормально кодят?
Давайте я объясню, как устроены опасные атаки, на примере с разработчиком Василием, который работает в интернет-магазине и которому начальство подкидывает разные ***интересные*** задачки.
Да, забыл представиться — я Артемий Богданов, CHO Start X.
Атака на десериализацию
Сначала немного теории.
Сериализацией называют процесс преобразования состояния объекта в форму, пригодную для сохранения или передачи. Неважно, в какой именно формат преобразуются данные объекта — в двоичный, XML, JSON или даже в обычную текстовую строку. Все это будет сериализацией.
Десериализация — это обратный сериализации процесс восстановления состояния объекта из сохраненных данных. Такими данными может быть полученный по сети поток байтов, параметр HTTP-запроса, введенные в форму данные или информация, прочитанная из файла.
Десериализация может быть реализована как обычный парсинг, в результате которого восстанавливаются поля данных. Такая десериализация не представляет опасности.
Но если помимо данных сохраняются и восстанавливаются еще и метаданные — классы, типы и методы, — то десериализация может создать угрозу. Это происходит в тех случаях, когда сериализованные данные модифицируются, контролируются посторонними или формируются из пользовательского ввода.
В зависимости от приложения злоумышленники могут сконструировать строку данных, которая после десериализации вызовет выполнение произвольного кода на сервере или приведет к другим незапланированным последствиям, например, к обходу системы аутентификации, раскрытию конфиденциальных данных или отказу в обслуживании.
Таким образом, десериализация превращается в источник угроз, как только у злоумышленника появляется возможность добраться до сериализованных данных и изменить их.
Как работает атака на десериализацию
В канун черных пятниц, киберпонедельников и рождественских распродаж к программисту Василию врывается начальник и требует, чтобы он срочно что-то сделал: база данных интернет-магазина работает на пределе. Нужно снизить нагрузку на базу и обеспечить запас мощности, иначе когда новые посетители начнут скупать товары со скидками, сайт просто ляжет.
Василий проанализировал код и придумал, как снизить нагрузку. Для этого он использовал следующий подход:
1. Отказался от хранения состояния корзины пользователя в базе данных. Вместо этого данные корзины сериализуются с помощью pickle и сохраняются в cookie пользователя.
Код на стороне сервера:
2. При каждом запросе данные корзины извлекаются из cookie и десериализуются для использования на сервере.
Код десериализации:
Решение Василия позволило убрать корзины пользователей из базы. В результате и место на сервере экономится, и нагрузка снижается, ведь запросов к базе стало меньше.
Цель вроде бы достигнута, начальник обещает премию, но теперь в приложении появилась уязвимость десериализации. Причина в том, что сохраненные на стороне пользователя данные доступны для изучения и модификации.
Возможные действия злоумышленника
1. Анализ формата данных. Злоумышленник замечает, что данные корзины хранятся в cookie, изучает формат данных и понимает, что они сериализованы с использованием pickle.
2. Создание вредоносного payload. Злоумышленник создает вредоносный объект, который при десериализации выполнит произвольный код, и готовит из него полезную нагрузку (payload) в виде текстовой строки.
Пример вредоносного кода:
3. Подмена cookie. Злоумышленник подменяет cookie с данными корзины на свой вредоносный сериализованный объект.
Теперь, когда пользователь с таким модифицированным cookie посетит сайт, уязвимый сервер десериализует вредоносный объект, в результате чего выполнится произвольный код. В приведенном примере запустится back connect shell — уязвимый сервер подключится к серверу злоумышленника и предоставит ему доступ к консоли shell.
В других языках программирования и при использовании других библиотек сериализации эксплуатация может немного отличаться, но описанные принципы останутся теми же.
Как защититься от атаки на десериализацию
Откажитесь от десериализации данных, полученных из ненадежных источников. Это особенно важно для языков и фреймворков, где десериализация может выполнять произвольный код, таких как Python с pickle.
Используйте безопасные альтернативы для сериализации. Вместо потенциально опасных методов сериализации, подобных pickle в Python, рассмотрите менее рискованные форматы или методы передачи данных. Возможно, вам вообще не нужна сериализация в классическом смысле, и достаточно сохранить данные в JSON «вручную».
Защитите сериализованные данные от модификации с помощью шифрования, подписи или хеширования.
Проверяйте и обезвреживайте входные данные перед обработкой. Убедитесь в отсутствии подозрительных элементов, а также в том, что тип и формат данных соответствуют ожидаемым.
Используйте принцип наименьших привилегий. Ограничьте доступ и права выполнения кода на сервере, чтобы снизить потенциальный ущерб от вредоносных действий, если уязвимость будет эксплуатироваться.
Регулярно обновляйте все используемые библиотеки и фреймворки до последних версий, чтобы воспользоваться исправлениями известных уязвимостей.
Многоликий XSS
XSS (Cross-Site Scripting, межсайтовый скриптинг) — это уязвимости веб-приложений, с помощью которых в страницы сайта можно внедрить вредоносные скрипты. Эти атаки используются для кражи данных, манипуляции контентом веб-страницы или перехвата контроля над аккаунтом пользователя.
Довольно долго считалось, что существует три типа XSS-атак:
хранимые (stored),
отраженные (reflected),
DOM XSS.
Постепенно выяснилось, что вполне реально встретить как хранимые, так и отраженные DOM XSS.
Чтобы исключить путаницу, в середине 2012 года решили, что достаточно двух типов XSS: серверного и клиентского. При этом и серверные и клиентские XSS могут быть как хранимыми, так и отраженными.
Рассмотрим виды XSS-атак подробнее.
Серверный XSS
Серверный XSS возникает, когда данные из ненадежного источника включаются в HTTP-ответ сервера. Источником этих данных может быть пользовательский ввод, переданный с текущим HTTP-запросом, и тогда мы получим отраженный серверный XSS. А если в ответе используются сохраненные на сервере данные, ранее полученные от пользователя, реализуется хранимый серверный XSS.
В этом случае уязвимость находится в коде на стороне сервера, а браузер просто отображает ответ и выполняет содержащийся в ответе скрипт.
Клиентский XSS
Клиентский XSS возникает, когда ненадежные данные используются для обновления DOM с помощью небезопасного вызова JavaScript (например, с помощью innerHTML). Вызов JavaScript считается небезопасным, если его могут использовать для внедрения кода в DOM. Источником данных также может быть запрос или сохраненная информация. Таким образом, возможен как Reflected Client XSS, так и Stored Client XSS.
Хранимые XSS (Stored XSS)
В этом случае внедренный вредоносный скрипт постоянно хранится в базе данных веб-приложения. Сервер выполняет его, когда пользователь посещает сайт. Код JavaScript будет выполняться браузером так же, как он записан в базе данных приложения, поэтому эта атака не требует никаких действий со стороны пользователя.
Отраженные XSS (Reflected XSS)
Отраженные XSS появляются, когда веб-приложение получает от пользователя данные, а в ответ выводит их небезопасным образом. Это может привести к тому, что злоумышленник передаст в браузер код, который будет немедленно исполнен.
Для использования этой разновидности уязвимости злоумышленнику нужно отправить вредоносную ссылку пользователю и убедить его перейти по ней.
Как работают XSS-атаки
Василий еще не получил премию за оптимизацию сайта, а начальник снова на пороге. На этот раз ему понадобились комментарии покупателей к товарам.
Чтобы не усложнять, Василий добавил на страницу с карточкой товара текстовое поле для комментариев. После отправки они отображаются в карточке товара.
Вот как это выглядит в коде:
Василий доволен. Ему удалось быстро выполнить поставленную задачу. Комментарии работают, начальник похвалил, только вот код снова оказался с уязвимостями.
Текст отзыва вставляется непосредственно в HTML без какой-либо обработки и санитизации. Если злоумышленник добавит в отзыв JavaScript-код, он будет встроен в HTML-код страницы и выполнится при отображении отзыва.
Возможные действия злоумышленника
Представим, что злоумышленник вставил следующий JavaScript-код в текстовое поле отзыва:
Вот что будет происходить каждый раз, когда посетитель интернет-магазина откроет карточку товара с этим отзывом:
Сохраненный вместе с отзывом скрипт выполнится и добавит на страницу невидимое изображение — HTML-элемент img с src, указывающий на сервер злоумышленника.
Все cookies пользователя соберутся и закодируются с помощью encodeURIComponent для корректной передачи на сервер злоумышленника.
При загрузке невидимого изображения браузер отправит запрос на сервер злоумышленника и передаст закодированные cookies в параметре запроса.
Сервер злоумышленника обработает запрос и извлечет из параметра cookies.
Таким образом злоумышленник будет получать cookie всех покупателей, которые зашли на зараженную страницу товара. Располагая cookie, хакер может зайти на сайт интернет-магазина от имени каждого из этих пользователей и совершить злонамеренное целевое действие, например, изменить адрес доставки пользовательских заказов.
Способы эксплуатации XSS-уязвимости могут меняться в зависимости от настроек сервера, флагов cookie, способа вывода информации и контекста вывода — от того, как именно и куда выводятся пользовательские данные.
Как защититься от XSS-атак
Найти XSS-уязвимость на сайте довольно легко — злоумышленнику достаточно отправлять запросы с вредоносным кодом и анализировать ответ сервера.
А вот устранение XSS часто становится сложным заданием, поскольку требует глубокого понимания контекстов и способов вывода информации в браузер. Именно от этого зависит выбор методов безопасной обработки данных.
Вот ключевые моменты, которые помогут реализовать комплексную защиту от XSS:
Контекстно-зависимое кодирование. Кодируйте и экранируйте данные при выводе на страницу в соответствии с контекстом вывода.
Валидация. Проводите строгую проверку полученных от пользователей данных, ограничивая их типы и форматы.
Флаги Cookies. Используйте флаги HttpOnly и Secure для cookies, чтобы защитить их от доступа через клиентские скрипты и разрешить передачу только через зашифрованные соединения.
Content Security Policy (CSP). Настройте CSP, чтобы предотвратить загрузку вредоносных скриптов на ваши страницы, ограничивая источники, с которых можно загружать скрипты.
Сложнее всего разобраться в правильном выборе метода кодирования или экранирования, поэтому давайте вернемся в наш интернет-магазин и поможем Василию разобраться с контекстом вывода информации.
В приведенном коде данные выводятся в теге «p». Это самый распространенный вариант. В данном случае пользовательский ввод необходимо html-кодировать, то есть перевести все обнаруженные в пользовательском вводе спецсимволы в html-сущности.
В php это можно сделать с помощью функции htmlspecialchars:
Теперь, если злоумышленник попробует вставить новый тег, например, <script> — он будет html-кодирован, превратится в <script> и не будет интерпретироваться браузером.
Добавив всего один вызов функции, мы обезопасили интернет-магазин от XSS-уязвимости в комментариях. А вот если бы контекст вывода информации был другой, например, данные выводились в css, как строка в javascript код, либо в url, или если бы они выводились не средствами бэкенда, а через DOM, то стратегия защиты, выбор способа кодирования, экранирования или санитизации были бы другими.
К сожалению, в большинстве фреймворков эти контексты не учитываются, и защита ограничивается html-кодированием. Не рассчитывайте, что использование популярного фреймворка полностью защитит вас от XSS.
Чтение произвольных файлов (Arbitrary File Reading)
Суть этой уязвимости понятна из названия: атакующий может получить доступ к файлам на сервере, которые не должны быть доступны для чтения через веб-приложение. Это могут быть, например, системные и конфигурационные файлы, файлы баз данных, исходные коды приложения и другая чувствительная информация.
Как происходит чтение произвольных файлов
Практически всегда возможность чтения произвольных файлов появляется, когда права доступа к файлам и директориям настроены некорректно, а для обращения к ним используется пользовательский ввод, который никак не валидируется.
Вернемся к Василию и посмотрим, как проявляется эта уязвимость. Он как раз получил очередное задание от начальника — сделать, чтобы покупатели могли просматривать электронные счета-фактуры своих заказов.
Вот что сделал Василий:
Документы генерируются в формате PDF и сохраняются в специальной директории на сервере. Для просмотра счета-фактуры нужно перейти по ссылке:
https://super-shop.ru/download_invoice.php?invoice_id=123
Код для скачивания счета-фактуры выглядит следующим образом:
Все работает, счета-фактуры отображаются, но в интернет-магазине появилась новая уязвимость.
Возможные действия злоумышленника
Значение параметра invoice_id конкатенируется с путем к файлу, но при этом не проводится никакой проверки или санитизации этого параметра. Это позволяет злоумышленнику манипулировать параметром invoice_id для доступа к любым файлам на сервере.
Например, злоумышленник может изменить URL вот так:
https://super-shop.ru/download_invoice.php?invoice_id=../../config.php
или так
https://super-shop.ru/download_invoice.php?invoice_id=../../../../../../etc/passwd
В ответ на эти запросы злоумышленник получит содержимое файла конфигурации config.php, в котором могут содержаться пароли базы данных, или увидит список пользователей сервера, хранящийся в файле /etc/passwd.
Как защититься от чтения произвольных файлов
Проверяйте пользовательский ввод, чтобы предотвратить нежелательные запросы к файлам. Проверяйте допустимость имен файлов, ограничивайте символы, проверяйте пути.
Используйте безопасные методы доступа к файлам. Вместо прямого формирования пути к файлу, используйте безопасные методы для доступа к файлам или используйте специализированные библиотеки.
Убедитесь, что ваше приложение или сервис работает с минимально необходимыми правами.
Разделите приложения и их данные, чтобы предотвратить утечку информации между различными частями системы.
Настройте системы мониторинга, чтобы отслеживать необычные или подозрительные действия. Это может помочь обнаружить атаку и вовремя на нее среагировать.
Обучите разработчиков принципам написания безопасного кода, чтобы уменьшить вероятность ошибок, приводящих к уязвимостям.
Выводы
Не существует универсального способа разом решить все проблемы с помощью одного простого действия. Если кто-то утверждает, что система защиты A или фреймворк B сделают ваше приложение абсолютно неуязвимым, скорее всего, он чего-то не знает, недоговаривает или банально пытается вас обмануть.
По моему опыту наибольший эффект дает фокус на процессах и людях.
Фокус на процессах означает, что безопасность должна не просто присутствовать в компании в виде отдельного подразделения, а стать обязательной частью процесса разработки. Это позволит внедрять требования к безопасности на этапе разработки продуктов — при проектировании, выборе архитектуры и технологического стека.
Для этого нужно, чтобы сотрудники отдела безопасности занимались не формальным контролем разработки, а полноценно участвовали в написании кода, анализировали инфраструктурные проблемы, следили за тестированием и отчетами статических и динамических анализаторов кода, консультировали программистов и помогали устранять выявленные уязвимости.
Фокус на людях означает, что руководство продуктовой команды осознает: ключевые аспекты в написании безопасного кода — это понимание основ безопасности, осведомленность о распространенных уязвимостях и методиках их предотвращения.
Для этого требуются инвестиции в обучение разработчиков, но в долгосрочной перспективе это помогает минимизировать риски и, самое главное, экономить деньги компании. Ведь исправление багов после релиза стоит дорого и тратит ресурсы разработки.
Я помогаю разрабатывать курсы и практические задания на платформе Start EDU. Мы сделали ее, чтобы научить разработчиков писать безопасные программы. Если вам интересно, можете запросить демо и посмотреть, как у нас все устроено:)
Комментарии (10)
vesper-bot
18.01.2024 09:24По мне, правильнее как раз не допускать десериализации данных от клиента вообще точка абзац, потому что клиент нынче заведомо зловредный, и если есть хоть какая-то возможность ему урвать денег, он это сделает. И лично я бы за вывод сериализованных данных на сторону клиента насовал бы по башке и откатил изменения вместо каких-то там премий.
mayorovp
18.01.2024 09:24+1Без десериализации вы нормального API не сделаете, никак.
Надо именно что использовать безопасные десериализаторы, которые десериализуют только данные, но никогда сами не загружают никакого кода.
gleb_l
18.01.2024 09:24ну и в стандартный набор классических ляпов еще и SQL-инъекцию. Попросил как-то начальник Васю добавить в интернет-магазин поиск товаров по подстроке... Дальше понятно.
Если серьезно - то валидация и экранирование всего, что пришло не из доверенного кольца - это база и основа.
mayorovp
18.01.2024 09:24+2Валидация вещь полезная, но вот экранировать лучше бы данные не при вводе, а при выводе.
В конце концов не важно откуда пришла строка содержащая спецсимволы, от пользователя или от админа, если есть способ корректно её вывести - надо именно этот способ и использовать.
gleb_l
18.01.2024 09:24При выводе конечно. Я наверное неточно выразился. Валидация - процесс проверки входных данных на ОДЗ. Экранирование - обеспечение соответствия выводимых данных правилам и ограничениям среды.
Для разных систем разные алгоритмы экранирования. Мы можем выводить одни и те же данные в грид бинарного приложения, в NRZ-физический канал, и на html-страницу.В любом случае, крайне желателен принцип неискажения данных - у кого-то в конце концов может быть фамилия O’Brien, а не O'Brien, и именно так она должна лежать в БД. Исключение - законодательные ограничения и совсем узкие случаи (например, хранение бинарного кода вируса в качестве payload где-нибудь в BLOB БД - пусть даже и в лаборатории ИБ для целей анализа) - не факт, что здесь не нужно его деактивировать каким-нибудь XORом даже перед записью в базу ;).
В эпоху, когда все в Интернете делалось вручную, это все в основном соблюдалось адекватными инженерами.
Сейчас же при формировании html-вывода вручную в 90% случаев энкода не будет, а про правильный URI-энкодинг помнят вообще единицы. Все полагаются вместо этого на автоматические анализаторы сабмита форм не пропускают невинные амперсанды и одинарные кавычки, несмотря на то, что это примерно так же эффективно, как поиск врагов по цвету паспорта )
Apoheliy
18.01.2024 09:24-1Не существует универсального способа разом решить все проблемы с помощью одного простого действия
Ну вроде бы ответ очевиден - и в том или ином виде рекомендуется "ехспертами":
например, смотрим статью хабра Компьютерная безопасность страдает от устаревших технологий / Хабр (habr.com)
И этот ответ: отказываемся от текущей версии Web-а. ЭТО НЕ ОЗНАЧАЕТ отказ от интернета, интернет-магазинов и т.д. Интернет (и вообще сети) - это не только Web.
Вообще, получается так, что когда хотят упростить жизнь всем вокруг (sql, например, задумывался как возможность всем получать нужные данные) в результате создают массу проблем. Благими намерениями вымощена дорога в Ад.
diakin
Я конечно мимокрокодил, но выглядит как "оторвите зад от стула и сделайте хоть что-нибудь.."