Неочевидные угрозы: как защититься от атак на десериализацию, XSS и чтение произвольных файлов

Кадр из сериала «Слово пацана»

Кадр из сериала «Слово пацана»

Злоумышленники могут успешно атаковать 98% веб-приложений. И это не просто громкие цифры, а данные из исследования Positive Technologies. Как такое возможно, если есть инструменты и практики типа SAST, DAST и WAF, а разработчики вроде бы нормально кодят?

Давайте я объясню, как устроены опасные атаки, на примере с разработчиком Василием, который работает в интернет-магазине и которому начальство подкидывает разные ***интересные*** задачки. 

Да, забыл представиться — я Артемий Богданов, CHO Start X.

Атака на десериализацию

Сначала немного теории. 

Сериализацией называют процесс преобразования состояния объекта в форму, пригодную для сохранения или передачи. Неважно, в какой именно формат преобразуются данные объекта — в двоичный, XML, JSON или даже в обычную текстовую строку. Все это будет сериализацией. 

Десериализация — это обратный сериализации процесс восстановления состояния объекта из сохраненных данных. Такими данными может быть полученный по сети поток байтов, параметр HTTP-запроса, введенные в форму данные или информация, прочитанная из файла. 

Десериализация может быть реализована как обычный парсинг, в результате которого восстанавливаются поля данных. Такая десериализация не представляет опасности. 

Но если помимо данных сохраняются и восстанавливаются еще и метаданные — классы, типы и методы, — то десериализация может создать угрозу. Это происходит в тех случаях, когда сериализованные данные модифицируются, контролируются посторонними или формируются из пользовательского ввода.  

В зависимости от приложения злоумышленники могут сконструировать строку данных, которая после десериализации вызовет выполнение произвольного кода на сервере или приведет к другим незапланированным последствиям, например, к обходу системы аутентификации, раскрытию конфиденциальных данных или отказу в обслуживании.

Таким образом, десериализация превращается в источник угроз, как только у злоумышленника появляется возможность добраться до сериализованных данных и изменить их. 

Как работает атака на десериализацию

В канун черных пятниц, киберпонедельников и рождественских распродаж к программисту Василию врывается начальник и требует, чтобы он срочно что-то сделал: база данных интернет-магазина работает на пределе. Нужно снизить нагрузку на базу и обеспечить запас мощности, иначе когда новые посетители начнут скупать товары со скидками, сайт просто ляжет. 

Василий проанализировал код и придумал, как снизить нагрузку. Для этого он использовал следующий подход:

1. Отказался от хранения состояния корзины пользователя в базе данных. Вместо этого данные корзины сериализуются с помощью pickle и сохраняются в cookie пользователя.

Код на стороне сервера:

998ae7a1739cd94ec9d615965c70b5d9.png

2. При каждом запросе данные корзины извлекаются из cookie и десериализуются для использования на сервере.

Код десериализации:

a6adcb299f27d81add61038b7fdee935.png

Решение Василия позволило убрать корзины пользователей из базы. В результате и место на сервере экономится, и нагрузка снижается, ведь запросов к базе стало меньше. 

Цель вроде бы достигнута, начальник обещает премию, но теперь в приложении появилась уязвимость десериализации. Причина в том, что сохраненные на стороне пользователя данные доступны для изучения и модификации. 

Возможные действия злоумышленника

1. Анализ формата данных. Злоумышленник замечает, что данные корзины хранятся в cookie, изучает формат данных и понимает, что они сериализованы с использованием pickle. 

2. Создание вредоносного payload. Злоумышленник создает вредоносный объект, который при десериализации выполнит произвольный код, и готовит из него полезную нагрузку (payload) в виде текстовой строки.

Пример вредоносного кода:

3e47ccbe708c149d91d76b551d41d6f2.png

3. Подмена cookie. Злоумышленник подменяет cookie с данными корзины на свой вредоносный сериализованный объект. 

Теперь, когда пользователь с таким модифицированным cookie посетит сайт, уязвимый сервер десериализует вредоносный объект, в результате чего выполнится произвольный код. В приведенном примере запустится back connect shell — уязвимый сервер подключится к серверу злоумышленника и предоставит ему доступ к консоли shell.

В других языках программирования и при использовании других библиотек сериализации эксплуатация может немного отличаться, но описанные принципы останутся теми же.

Как защититься от атаки на десериализацию

  1. Откажитесь от десериализации данных, полученных из ненадежных источников. Это особенно важно для языков и фреймворков, где десериализация может выполнять произвольный код, таких как Python с pickle.

  2. Используйте безопасные альтернативы для сериализации. Вместо потенциально опасных методов сериализации, подобных pickle в Python, рассмотрите менее рискованные форматы или методы передачи данных. Возможно, вам вообще не нужна сериализация в классическом смысле, и достаточно сохранить данные в JSON «вручную». 

  3. Защитите сериализованные данные от модификации с помощью шифрования, подписи или хеширования. 

  4. Проверяйте и обезвреживайте входные данные перед обработкой. Убедитесь в отсутствии подозрительных элементов, а также в том, что тип и формат данных соответствуют ожидаемым.

  5. Используйте принцип наименьших привилегий. Ограничьте доступ и права выполнения кода на сервере, чтобы снизить потенциальный ущерб от вредоносных действий, если уязвимость будет эксплуатироваться.

  6. Регулярно обновляйте все используемые библиотеки и фреймворки до последних версий, чтобы воспользоваться исправлениями известных уязвимостей.

Многоликий XSS 

XSS (Cross-Site Scripting, межсайтовый скриптинг) — это уязвимости веб-приложений, с помощью которых в страницы сайта можно внедрить вредоносные скрипты. Эти атаки используются для кражи данных, манипуляции контентом веб-страницы или перехвата контроля над аккаунтом пользователя.

Довольно долго считалось, что существует три типа XSS-атак:  

  • хранимые (stored),  

  • отраженные (reflected),

  • DOM XSS.

Постепенно выяснилось, что вполне реально встретить как хранимые, так и отраженные DOM XSS. 

Чтобы исключить путаницу, в середине 2012 года решили, что достаточно двух типов XSS: серверного и клиентского. При этом и серверные и клиентские XSS могут быть как хранимыми, так и отраженными. 

7d3a2a53a79745eb4999bf61ac463164.png

Рассмотрим виды 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-атаки

Василий еще не получил премию за оптимизацию сайта, а начальник снова на пороге. На этот раз ему понадобились комментарии покупателей к товарам. 

Чтобы не усложнять, Василий добавил на страницу с карточкой товара текстовое поле для комментариев. После отправки они отображаются в карточке товара.

Вот как это выглядит в коде:

f44bcd00c53691ca5a7977279e355a47.png

Василий доволен. Ему удалось быстро выполнить поставленную задачу. Комментарии работают, начальник похвалил, только вот код снова оказался с уязвимостями. 

Текст отзыва вставляется непосредственно в HTML без какой-либо обработки и санитизации. Если злоумышленник добавит в отзыв JavaScript-код, он будет встроен в HTML-код страницы и выполнится при отображении отзыва.

Возможные действия злоумышленника

Представим, что злоумышленник вставил следующий JavaScript-код в текстовое поле отзыва:

d47cbc2d5348e619b1dc2b19e1855707.png

Вот что будет происходить каждый раз, когда посетитель интернет-магазина откроет карточку товара с этим отзывом:

  1. Сохраненный вместе с отзывом скрипт выполнится и добавит на страницу невидимое изображение — HTML-элемент img с src, указывающий на сервер злоумышленника. 

  2. Все cookies пользователя соберутся и закодируются с помощью encodeURIComponent для корректной передачи на сервер злоумышленника.

  3. При загрузке невидимого изображения браузер отправит запрос на сервер злоумышленника и передаст закодированные cookies в параметре запроса.

  4. Сервер злоумышленника обработает запрос и извлечет из параметра cookies.

Таким образом злоумышленник будет получать cookie всех покупателей, которые зашли на зараженную страницу товара. Располагая cookie, хакер может зайти на сайт интернет-магазина от имени каждого из этих пользователей и совершить злонамеренное целевое действие, например, изменить адрес доставки пользовательских заказов.

Способы эксплуатации XSS-уязвимости могут меняться в зависимости от настроек сервера, флагов cookie, способа вывода информации и контекста вывода — от того, как именно и куда выводятся пользовательские данные. 

Как защититься от XSS-атак

Найти XSS-уязвимость на сайте довольно легко — злоумышленнику достаточно отправлять запросы с вредоносным кодом и анализировать ответ сервера. 

А вот устранение XSS часто становится сложным заданием, поскольку требует глубокого понимания контекстов и способов вывода информации в браузер. Именно от этого зависит выбор методов безопасной обработки данных.

Вот ключевые моменты, которые помогут реализовать комплексную защиту от XSS:

  1. Контекстно-зависимое кодирование. Кодируйте и экранируйте данные при выводе на страницу в соответствии с контекстом вывода. 

  2. Валидация. Проводите строгую проверку полученных от пользователей данных, ограничивая их типы и форматы.

  3. Флаги Cookies. Используйте флаги HttpOnly и Secure для cookies, чтобы защитить их от доступа через клиентские скрипты и разрешить передачу только через зашифрованные соединения.

  4. Content Security Policy (CSP). Настройте CSP, чтобы предотвратить загрузку вредоносных скриптов на ваши страницы, ограничивая источники, с которых можно загружать скрипты.

Сложнее всего разобраться в правильном выборе метода кодирования или экранирования, поэтому давайте вернемся в наш интернет-магазин и поможем Василию разобраться с контекстом вывода информации. 

05ad2bb6dc702c54c4f2c357f1548ed2.png

В приведенном коде данные выводятся в теге «p». Это самый распространенный вариант. В данном случае пользовательский ввод необходимо html-кодировать, то есть перевести все обнаруженные в пользовательском вводе спецсимволы в html-сущности. 

В php это можно сделать с помощью функции htmlspecialchars:

40c65d08255f98b89a014b30c752f3cd.png

Теперь, если злоумышленник попробует вставить новый тег, например,