Некорректная длина BSON приводит к сбою драйвера MongoDB Go, libmongocrypt ошибается на дублирующемся ключе

MongoDB

MongoDB закрыла ошибки разбора данных сразу в двух своих компонентах - Go-драйвере и библиотеке libmongocrypt, которую применяют для шифрования отдельных полей на стороне клиента. Первый набор правок касается обработки BSON, двоичного формата, в котором MongoDB хранит и передаёт документы. Второй относится к чтению ключей шифрования. Под угрозу попадают сервисы, которые получают BSON из недоверенных источников: от сторонних серверов, прокси или промежуточных приложений. Исправления вышли в Go Driver версий 2.9.0 и 2.9.2, а также в libmongocrypt 1.20.5. Всё, что старше этих сборок, содержит проблемный код.

Детали уязвимостей

Любой BSON-документ начинается с заголовка, где записана его длина. Именно это число разборщик читает первым, и от него зависят все дальнейшие шаги: смещения полей, размер выделяемой памяти, границы буфера. Разборщик обязан проверять заявленную длину до того, как доверится ей. Если проверки нет, приложение опирается на значение, которое пришло извне, а значит, может быть каким угодно.

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

Паника в Go означает аварийное завершение программы, если её не перехватить в том же потоке выполнения. Для сетевого сервиса это отказ в обслуживании: одного некорректного документа достаточно, чтобы оборвалась обработка запроса, а при неудачной архитектуре - и весь процесс. Похожую проблему разбирали раньше: отдельный тикет описывал панику на документе нулевой длины, и новая правка закрывает целое семейство случаев, а не единичный. В трекере MongoDB эти изменения помечены как обычные ошибки, одна из них получила приоритет Major, остальные - Minor. Разработчики при этом прямо пишут, что входные данные для перечисленных точек входа недоверенные.

Второй дефект в Go-драйвере связан с арифметикой. При вычислении длины полезной нагрузки максимальное 32-битное число увеличивалось ещё на четыре байта и переполняло разрядную сетку, превращаясь в отрицательное значение. Проверка, которая должна была отсечь такой результат, отсутствовала. Воспроизвести это напрямую в приложении непросто: нужно либо работать с низкоуровневой библиотекой BSON напрямую, либо получать искажённые документы от сервера. Как правило, разработчики не рассчитывают на ошибки на стороне сервера, но именно поэтому проверка границ остаётся обязательной. Правка вошла в версию 2.9.0.

Проблема в libmongocrypt устроена иначе. Эта библиотека отвечает за шифрование полей на стороне клиента: приложение шифрует чувствительные данные до отправки, и сервер видит уже нечитаемое содержимое. Для этого используются ключи данных и ключи шифрования ключей, или KEK. В описании ключа есть поле masterKey, которое указывает на KEK. Если такое поле встречалось в документе дважды, повторный разбор приводил к тому же ключу шифрования ключей и оставлял объект в некорректном состоянии. Отдельная часть правки устранила и последствия неудачного разбора KEK, когда объект сохранял признаки незавершённой операции. Исправление помечено приоритетом Major и связано с внутренним тикетом SECBUG-4375.

Для сервисов, которые полагаются на шифрование на стороне клиента, такие сбои чувствительны. Операция шифрования или расшифрования может завершиться ошибкой, а разбирательство с ключами занимает время и требует доступа к обоим концам схемы. Пока ключ хранится корректно, данные защищены, но любая путаница в состоянии разбора бьёт по доступности: приложение не сможет ни записать защищённое поле, ни прочитать его обратно. Комбинация двух наборов правок в одном выпуске означает, что обновляться нужно целиком, а не выборочно, потому что код шифрования и код разбора BSON работают в одном процессе.

Обновление закрывает вопрос. Для Go-драйвера достаточно версии 2.9.2, она включает и исправление переполнения, и проверку минимальной длины. Для libmongocrypt нужна версия 1.20.5. Проверить стоит не только сам сервис, но и всё, что подключено к MongoDB-совместимым реализациям, включая тестовые и внутренние стенды, которые обычно отстают от основной ветки. Отдельного внимания заслуживают приложения, получающие документы от внешних систем: именно для них паника на разборе превращается из теоретической ситуации в реальную.

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

Ссылки

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