Две уязвимости в официальных драйверах MongoDB затрагивают приложения на PHP и Java. В библиотеке Laravel MongoDB до версии 5.11.0 ошибка в построителе запросов превращает проверку равенства в произвольное условие отбора. В результате приложение может отдать первую попавшуюся запись или удалить все документы коллекции. В Java Driver до версии 5.11.1 отмена зашифрованной операции приводит к аварийному завершению процесса. Обе проблемы закрыты в указанных версиях.
Детали уязвимостей
Первая уязвимость касается построителя запросов Laravel MongoDB для PHP. Метод построения условия принимает имя поля, оператор и значение. Если разработчик указывает знак равенства и передаёт в качестве значения массив, драйвер подставляет этот массив в фильтр без изменений. Однако MongoDB трактует такой массив как документ с операторами, а не как обычное значение. Поэтому проверка на равенство перестаёт быть проверкой и становится произвольным условием.
Практическое следствие зависит от того, куда попадает подставленный массив. Оператор "не равно пустому значению" заставляет поиск по идентификатору вернуть первый документ коллекции. Тот же оператор в операции удаления по идентификатору очищает коллекцию целиком. Кроме того, приложение, которое передаёт в условие необработанный пользовательский ввод, начинает совпадать с любой записью. В итоге злоумышленник получает доступ к чужим данным или лишает владельца всех данных сразу.
Внутренние вызовы поиска и удаления по идентификатору проходят через тот же код. Следовательно, даже аккуратно написанное приложение уязвимо, если оно передаёт в них значение из запроса без проверки типа. Идентификатор, пришедший от пользователя, может оказаться массивом с оператором. Ошибку описали в отчёте SECBUG-4402 исследователи Aegis-IH.
В версии 5.11.0 значение в трёхаргументной форме условия сравнивается как литеральный документ. Для этого драйвер оборачивает массив в явный оператор точного равенства. Двухаргументная форма сохраняет прежнее поведение, поскольку она документирована как способ задать документ с операторами, например для поиска по вхождению в список. Разработчикам стоит учитывать, что две формы записи условия перестали быть взаимозаменяемыми, когда значение является массивом. Об этом сказано в руководстве по обновлению и в документации построителя запросов.
Исправление потребовало доработки. Laravel разворачивает условие, переданное массивом, в последовательность вызовов с тремя аргументами. Поэтому первая версия правки не отличала такие вызовы от явного указания равенства. В результате ломались штатные сценарии: передача сырого фрагмента фильтра и сокращённая запись условия по одному полю. Обе конструкции начали сравниваться как литеральные документы. Разработчики вернули элементам массива поведение двухаргументной формы и добавили тесты на каждый случай.
Вторая уязвимость относится к реактивному драйверу Java. Она затрагивает шифрование на стороне клиента. Драйвер удерживает нативный контекст библиотеки libmongocrypt, то есть набора криптографических функций для работы с ключами, на всё время операции. При отмене, будь то разрыв соединения или истечение времени ожидания, контекст освобождается в том потоке, который выполняет отмену. Проверки, что другой поток ещё работает внутри шага конечного автомата, нет.
Освобождённая область памяти остаётся доступной для записи. Один из потоков продолжает работу и пишет в неё. В результате виртуальная машина Java аварийно завершается либо повреждаются данные в компоненте, который хранит ключи шифрования. Второй вариант опаснее, поскольку не оставляет явных следов. Проблему подтвердили практикой: сервис на Spring Boot WebFlux погиб от одного прерванного HTTP-запроса, падение повторилось в двух запусках из двух. Идентификатор рекомендации - GHSA-c4c8-c376-3p6c.
Ошибка шире, чем считалось при регистрации. Уязвимы реактивные потоки драйвера, модуль mongodb-crypt, а также драйверы для Kotlin и Scala, которые наследуют поведение. Под удар попадают приложения, получающие данные доступа к службе управления ключами (KMS) по запросу. Речь идёт о пустом документе провайдера вместе с поставщиком свойств; локальные хранилища ключей затронуты так же, как облачные. Синхронный драйвер не уязвим, потому что работает в одном потоке и освобождает контекст там же, где создал.
Частота срабатывания ограничена. Библиотека запрашивает учётные данные только при промахе кеша ключей. Поэтому окно уязвимости закрывается после прогрева кеша и открывается вновь при истечении ключа, примерно раз в минуту. Замеры показали: 25 одновременных прерванных запросов не вызвали падения, при этом учётные данные выбирались дважды. То есть опасность реализуется примерно раз на ключ за время его жизни, а не на каждый запрос.
В версии 5.11.1 обращения к нативной памяти и освобождение контекста защищены одним замком. Признак закрытия выставляется до освобождения. Вызовы после закрытия должны приводить к исключению, а само закрытие стало идемпотентным и безопасным при одновременном вызове. Замок охватывает только нативный вызов, поэтому отменяющий поток не ждёт сетевого обхода. Разработчики добавили регрессионный тест, который отменяет операцию в середине. При этом однострочную проверку признака закрытия признали недостаточной: при её наличии воспроизведение удавалось в двадцати случаях из двадцати.
Обновление Java Driver до 5.11.1 и Laravel MongoDB до 5.11.0 закрывает описанные сценарии. Обе истории показывают общую черту: опасны не только ошибки в логике, но и несовпадение типов данных, а также гонки на путях отмены. Проверка типов на входе в фильтры снижает риск. Внимательное отношение к жизненному циклу нативных ресурсов помогает не меньше. Отдельной задачей остаётся блокирующее получение учётных данных по HTTP, которое не следует выполнять на реактивном пути.
Ссылки