Составной ключ кэша в Nginx допускает обход ограничений доступа и подмену ответов

NGINX

Исследователь Алекс Брумен из компании YesWeHack описал технику, которая превращает ключ кэша в самостоятельную точку атаки. Приём назвали cache key injection, то есть инъекция в ключ кэша. Под угрозой оказались серверы Nginx, работающие кэширующим прокси перед веб-приложением. При неудачной конфигурации злоумышленник обходит правила доступа, достаёт закрытые страницы из общего хранилища, вызывает отказ в обслуживании и добивается выполнения хранимого межсайтового скриптинга (XSS) у других посетителей сайта.

Детали

Кэш хранит ответы сервера и отдаёт их повторно, когда новый запрос совпадает с уже сохранённой записью. Совпадение определяется по ключу. Nginx собирает такой ключ из нескольких частей запроса: схемы, имени хоста, пути, строки параметров, cookie (небольших файлов, где браузер хранит настройки пользователя) и отдельных заголовков. Разработчики нередко добавляют в ключ заголовок Accept или языковую cookie, чтобы выдавать разным посетителям разный контент.

Опасность появляется, когда значения склеивают подряд, без разделителей. Границы между частями исчезают, и два разных набора значений дают одну и ту же строку. Простой пример выглядит так. Законный запрос к странице /home с заголовком Accept: */* порождает тот же ключ, что и запрос злоумышленника к несуществующему пути /h с заголовком Accept: ome*/*. Строка складывается одинаково, хотя запросы разные. Если первой в кэш попадёт чужая версия ответа, её получат все, кто обратится к главной странице.

Классическое отравление веб-кэша работает иначе. Обычно атакующий ищет заголовки и параметры, которые в ключ не входят, и пытается протащить через них вредоносное значение. Брумен пошёл от обратного и занялся фрагментами, которые в ключе уже присутствуют. Разделять их нечем, поэтому значение одного фрагмента перетекает в соседний.

Практический эффект заметнее всего там, где кэш связан с разграничением прав. В одном из примеров исследователя сервер пускал на страницу администратора только с локального адреса. Злоумышленник запросил короткий путь из двух букв, а остаток слова перенёс в соседний заголовок. Проверка прав смотрела на укороченный путь и пропускала запрос. Итоговый ключ совпадал с ключом закрытой страницы, и атакующий получал её содержимое из общего кэша. Взаимодействие пользователя здесь не требуется.

Отказ в обслуживании строится похожим образом, но с другим порядком действий. Злоумышленник обращается к несуществующему адресу, чей ключ совпадает с ключом реальной страницы. Сервер отвечает ошибкой 404 и сохраняет её в кэше. Посетители, которые позже открывают настоящую страницу, вместо неё получают чужую ошибку. Такое состояние держится до истечения срока хранения записи или до её ручной очистки.

Наиболее серьёзный сценарий связан с путаницей между HTTP и HTTPS. Он требует совпадения нескольких условий. Ключ должен начинаться со схемы и имени хоста, сервер принимает произвольное значение заголовка Host, а приложение отвечает одинаковым содержимым на обоих портах и не перенаправляет HTTP на HTTPS. Тогда последнюю букву в слове https переносят в начало имени хоста. Запрос по HTTP с хостом, начинающимся с s, даёт тот же ключ, что и законный запрос по HTTPS. Если приложение подставляет имя хоста в адрес подключаемого скрипта, отравленная страница начинает загружать чужой код. Так отравление кэша перерастает в хранимый межсайтовый скриптинг.

Ещё один сценарий касается пограничного кэша Cloudflare. Запросы с заголовком Authorization обычно проходят мимо кэша Cloudflare, если только источник не разрешил обратное. Nginx такого правила по умолчанию не применяет. В результате запрос доходит до внутреннего кэша Nginx и отравляет его, хотя внешний слой остаётся нетронутым. Позже пользователь без заголовка авторизации получает испорченный ответ из источника.

Масштаб последствий зависит от нескольких обстоятельств: какой именно адрес попал под удар, сколько времени живёт запись в кэше и сколько людей пользуется общим хранилищем. Отравленная запись может разойтись по нескольким уровням кэширования, и тогда исправлять приходится каждый из них.

Защита начинается с отказа от простой склейки. Хеширование итоговой строки не помогает, поскольку одинаковая строка даёт одинаковый хеш независимо от способа её сборки. Администраторам стоит добавлять разделители между частями ключа или применять структурное кодирование, проверять заголовок Host и разводить поведение HTTP и HTTPS. Отдельная мера касается авторизованных запросов: их ответы не должны попадать в общий кэш. В Nginx для этого есть настройки, исключающие такие обращения из кэширования. Если отравленная запись уже лежит в хранилище, её нужно удалить.

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

Ссылки

Комментарии: 0