Уязвимость MongoDB (CVE-2025-14847 MongoBleed): Почему уязвимость MongoBleed не так опасна, как её описывают другие?
Авторы:
Александр Ястремской (Специалист по тестированию на проникновение компании Compliance Control)
Амиржан Куаныш (Специалист по тестированию на проникновение компании Compliance Control)
Внутренний отдел исследовательского центра компании Compliance Control провёл подробный ресёрч свежей уязвимости MongoBleed (CVE-2025-14847), опубликованной в конце прошлого года, и показал, что она не имеет такой практической опасности, как это утверждают другие исследователи безопасности.
Благодаря данной уязвимости злоумышленники могут считывать фрагменты памяти из серверного процесса MongoDB с возможностью извлекать пароли, токены аутентификации или части записей базы данных, которые не должны быть доступны.
MongoBleed напоминает старую уязвимость Heartbleed (CVE-2014-0160) в OpenSSL из-за схожести проблем безопасности. Обе уязвимости возникают из-за несоответствия заявленной и фактической длины данных (Length Mismatch), что также позволяет прочитать конфиденциальную информацию сервера. В 2014 году уязвимость OpenSSL потрясла ИТ-индустрию, показав, как простое несоответствие длин может привести к утечке гигабайтов конфиденциальных данных. Спустя 8 лет, в 2022 году, в MongoDB обнаружили CVE-2025-14847 — уязвимость, которую исследователи тут же окрестили MongoBleed. И это не просто сходство названий — это практически идентичный архитектурный просчёт, повторённый в другом продукте. Однако важно понимать, что в случае с MongoBleed проблема не настолько глобальна, как с OpenSSL, который используют повсеместно.
MongoDB — это документоориентированная система управления базами данных (СУБД) класса NoSQL, которая использует гибкие, самоописываемые документы в формате BSON (бинарный JSON) как основную единицу хранения данных, вместо таблиц и строк в реляционных СУБД.
Сама компания MongoDB Inc. оценивает опасность данной уязвимости на 7.5 баллов (CWE-130). NIST в свою очередь пока что оценку не дал.
MongoBleed затрагивает следующие версии MongoDB:
| Версия | Уязвимая версия | Исправленная версия |
| 8.2.x | 8.2.0 — 8.2.2 | 8.2.3 |
| 8.0.x | 8.0.0 — 8.0.16 | 8.0.17 |
| 7.0.x | 7.0.0 — 7.0.27 | 7.0.28 |
| 6.0.x | 6.0.0 — 6.0.26 | 6.0.27 |
| 5.0.x | 5.0.0 — 5.0.31 | 5.0.32 |
При запросе в поисковой системе Shodan можно убедиться, что у множества серверов открыт порт 27017 с уязвимой версией сервиса MongoDB во внешнюю сеть (например, MongoDB 8.0.11, которую поддерживает более 7.000 хостов). Это указывает на то, что масштаб проблемы вполне приличный.

Основная проблема, которая и повлекла за собой уязвимость, связана с заголовками сжатого протокола Zlib. Zlib — это популярная библиотека сжатия, которую MongoDB использует для уменьшения размера протокольного трафика. При определённых условиях MongoDB неправильно обрабатывает несовпадающие поля длины в этих сжатых заголовках протокола. Это может привести к тому, что сервер будет считывать данные из неинициализированных областей своей памяти и возвращать их подключающемуся клиенту — даже если этот клиент не прошёл аутентификацию.
В заголовке сжатого сообщения (opCompressed) есть поле, указывающее размер исходного (несжатого) сообщения. Сервер выделяет буфер именно под этот размер. Затем в этот буфер он распаковывает (декомпрессирует) данные. Если фактический размер распакованных данных (uncompressedSize) меньше, чем размер, заявленный в заголовке (originalMessageLength), сервер не заполняет оставшуюся часть буфера нулями или не генерирует ошибку. Вместо этого он продолжает работать с этим буфером, считая его полностью валидным. Когда сервер формирует ответ (например, ошибку обработки), он читает данные из этого частично заполненного буфера. В ответ клиенту уходит всё содержимое буфера: сначала валидные данные, а следом — случайные байты из памяти, которые находились в RAM на момент выделения буфера. Это и приводит к утечке неинициализированной памяти.


Разберём работу уязвимости подробнее на примере опубликованного в сети эксплойта.
Эксплойт создаёт BSON-документы с полями увеличенной длины. В тот момент, когда сервер анализирует эти документы, он считывает имена полей из неинициализированной памяти, пока не встретит нулевой байт. Каждый запрос с разным смещением может привести к утечке данных из разных областей памяти.
Утечка может привести к следующим находкам:
- Внутренние журналы и состояние MongoDB
- Конфигурация механизма хранения WiredTiger
- Системные /proc данные (информация о памяти, сетевая статистика)
- Пути к контейнерам Docker
- UUID-соединения и IP-адреса клиентов
За эксплуатацию уязвимости в этом эксплойте отвечает функция send_probe. Она формирует сообщение для отправки на сервер при помощи сжатия сообщения библиотеки Zlib.

Для начала создаётся валидное поле для BSON-документа (content = b‘\x10a\x00\x01\x00\x00\x00‘), что указывает тип данных, имя поля и значение (int32 a=1). a:1 для BSON — это минимальный валидный BSON-документ, который нужен эксплойту для обмана сервера. Строка bson = struct.pack(‘<i’, doc_len) + content генерирует полный документ BSON, но с ошибкой длины сообщения и без нулевого байта. Сервер читает \x01\x00\x00\x00 как «значение = 1». Далее он переходит к следующей позиции для чтения следующего поля, и тогда возникает проблема, которая приводит к данной уязвимости.
После прочтения поля сервер:
- Ожидает либо следующее поле
- Либо \x00 (конец документа)
Но поскольку в эксплоте будет указано больше байт, чем в самом сообщении до сжатия и не будет нулевого байта \x00, то сервер продолжит читать дальше в памяти, думая, что там ещё 4989 байт BSON-полей.
Пример с валидного запроса:
correct_bson= (
b'\x0b\x00\x00\x00' + # длина: 11 байт
b'\x10' + b'a\x00' + b'\x01\x00\x00\x00' + # поле {"a": 1}
b'\x00' # конец документа
)
Пример в эксплойте:
exploit_bson = (
b'\x88\x13\x00\x00' + # ложная длина поля в 5000 байт
b'\x10' + b'a\x00' + b'\x01\x00\x00\x00' # поле {"a": 1}
# нет конечного нулевого байта!
)
Далее формируется тип сообщения op_compressed и сжимается при помощи библиотеки Zlib. op_compressed — это тип сообщения в протоколе MongoDB Wire Protocol, который позволяет отправлять сжатые данные.
MongoDB использует бинарный протокол (Wire Protocol), где каждый тип сообщения имеет свой числовой код.
По итогу эксплойт выполняет следующие шаги:
- Говорим серверу: «после распаковки будет 5500 байт»
- На самом деле после распаковки будет всего ~16 байт!
- Сервер выделит буфер размером 5500 байт
payload += struct.pack(‘B‘, 2) указывает, что для сжатия будет использоваться алгоритм zlib.
Далее формируется заголовок и тело данных сетевого пакета с поддельными данными и отправляется на сервер MongoDB. MongoDB на наше сообщение отвечает ошибкой, в которой случайно показывает кусочек своей памяти. Функция extract_leaks – это сборщик утекших данных.
Из ответа MongoDB (который содержит ошибку) вытаскивает случайные байты из постоянно меняющейся памяти сервера. Сборщик проверяет, достаточно ли данных для анализа. Поиск происходит по следующим паттернам:
- «field name ‘секретный_пароль_123’ is not valid for storage»
- «BSON field ‘OP_MSG.{случайные данные из памяти}’ is of unknown type»
Некоторые данные в памяти не являются текстом, а могут быть бинарными. Например:
- Ключи шифрования
- Хеши паролей
- Бинарные структуры данных
Поиск бинарных данных происходит по следующим паттернам:
- «Unknown BSON type: 115»
- «type 67 is not a valid BSON type»
Без этих паттернов эксплойт получил бы только текстовые части, потеряв бинарные данные.
После чего фильтруются шумы для извлечения конфиденциальных данных.
Схематично весь цикл работает следующим образом:

Чем больше перебор, тем больше разных областей памяти можно прочитать. Эксплойт методично прощупывает память сервера разными «зондами» (разными размерами) и собирает всё, что выплёскивается в ошибках. Как археолог, который копает на разной глубине и собирает артефакты.
Однако, несмотря на впечатляющий теоретический потенциал утечки памяти, описанный в документации, практическая эксплуатация CVE-2025-14847 сталкивается с фундаментальными техническими ограничениями, которые существенно снижают его реальную эффективность.
Проблема «холодной» памяти
Современные аллокаторы (особенно в долгоживущих процессах типа MongoDB) не возвращают «свежую» память из ядра.

Аллокаторы вроде jemalloc или glibc malloc активно кэшируют освобождённые блоки в своих внутренних структурах: «аренах» (разделяемых пулах для потоков) и «tcache» (thread-local cache, привязанном к конкретному потоку). При вызове free() данные в блоке не зануляются, а сам блок помещается в один из списков свободных блоков, сгруппированных по размерам (например, 8, 16, 32, 48 байт и т. д.). При последующем запросе памяти того же диапазона размеров аллокатор с высокой вероятностью вернёт этот же физический блок из кэша, не очищая его содержимое. В результате процесс получает не «чистый» нулевой кусок от ядра, а фрагмент, сохранивший артефакты предыдущей жизни приложения — старые указатели, флаги, части строк или структур данных.
Это делает состояние кучи «шумным» и недетерминированным для атакующего: невозможно предсказать, какие именно остатки окажутся в выделенной памяти, и извлечь конкретные секреты без точного контроля над размерами и временем освобождения.
Механизм переиспользования заложен в архитектуру намеренно:
Выделение памяти с помощью mmap(2) имеет существенное преимущество: выделенные блоки памяти всегда можно вернуть системе независимо друг от друга. С другой стороны, у использования mmap(2) есть некоторые недостатки: освобождённое пространство не попадает в список свободных для повторного использования при последующих выделениях памяти; память может расходоваться впустую, поскольку выделение должно быть выровнено по странице; кроме того, ядру приходится выполнять дорогостоящую операцию обнуления памяти, выделенной с помощью mmap(2). С учётом этих факторов по умолчанию для параметра M_MMAP_THRESHOLD установлено значение 128×1024.
Иными словами: всё, что меньше 128 КБ — а это подавляющее большинство типичных аллокаций MongoDB (BSON-буферы, строки, внутренние структуры), — никогда не получает свежую обнулённую память от ядра. Блок возвращается из бина как есть, с артефактами предыдущей жизни. Это подтверждается официальной документацией: согласно malloc(3), память возвращаемая аллокатором не инициализируется, то есть содержит ровно то, что находилось там до предыдущего free().
Проще говоря, даже если вы читаете память, вы получаете не то, что было «только что». Типичный сценарий работы аллокатора памяти в MongoDB:
Шаг 1. Клиент отправляет пароль
Предположим, легитимный клиент MongoDB аутентифицируется:
db.auth(«admin», «RealPassword123»)
Драйвер формирует пакет с паролем (строка «RealPassword123» занимает, скажем, 16 байт).
Шаг 2. Пароль попадает в буфер X (100 байт)
Серверная часть (обработчик аутентификации) выделяет буфер размером 100 байт через malloc(), копирует туда пароль и другие служебные данные (логин, timestamp). Пусть этот буфер называется Буфер X. Он находится в куче процесса.
Шаг 3. Через 50 мс буфер X освобождается
Аутентификация завершена. Сервер вызывает free(X).
Память не возвращается ядру (это было бы дорого, потому что каждый кусочек памяти ему нужен, и, если бы он отдавал обратно выделенные памяти, у него банально не осталось бы виртуальной памяти). Вместо этого аллокатор (jemalloc / glibc malloc) помечает блок как свободный и кладёт его в один из своих кэшей:
- tcache (thread-local cache) — для быстрой выдачи тому же потоку.
- арена (arena) — общий пул для потоков.
Размер блока — 100 байт. Он лежит в кэше для блоков размера ~96–112 байт (в зависимости от выравнивания).
Шаг 4. Через 500 мс поступает эксплойт-запрос на 8192 байта
Специальный запрос заставляет сервер выделить 8192 байта (например, большой BSON-документ с $where или $regex). Сервер вызывает malloc(8192).
Шаг 5. Аллокатор смотрит в кэши
Аллокатор ищет свободный блок подходящего размера:
- В tcache есть блок 100 байт → слишком мал.
- В аренах есть блок 100 байт → тоже мал.
- Блоки большего размера (например, 8 КБ) могут быть заняты или фрагментированы.
Аллокатор не станет разрезать большой блок на части ради 8 КБ (это неэффективно) и не склеит несколько мелких блоков в один (он так не работает — аллокаторы выдают непрерывные регионы).
Шаг 6. Аллокатор выдаёт буфер Y из другой области памяти
Аллокатор запрашивает у ядра свежую область размером 8 КБ либо берёт существующий свободный блок из списка больших блоков. Этот буфер Y не имеет никакого отношения к недавно освобождённому буферу X на 100 байт. Буфер Y содержит либо нулевую память (новый MAP_ANONYMOUS регион) или остатки других крупных выделений — предыдущий BSON-документ, результаты запроса, кэш индексов.
Шаг 7. Эксплойт читает старые BSON-документы, а не пароль
В буфере Y обнаруживаются остатки каких-то других структур. Пароль по-прежнему лежит в буфере X (100 байт) в tcache/арене — недосягаемый, потому что запрос был сделан на принципиально другой размер и аллокатор выдал принципиально другой регион.
Таким образом, даже если в программе существует уязвимость, позволяющая читать неинициализированную или освобождённую память, извлечь конкретные чувствительные данные (пароль, токен, ключ) часто не удаётся из-за стратегий работы аллокатора. Кэширование по размеру приводит к тому, что освобождённый блок остаётся «запертым» в пуле своего типоразмера. Эксплойт должен запрашивать блоки точно того же размера и из того же потока/контекста, иначе аллокатор выдаст другую память. Это превращает эксплуатацию из тривиального чтения «прошлого» в сложную игру с подбором размеров, временем и состязательностью.
Разные пулы памяти в MongoDB
А еще важно понимать, что MongoDB использует разные пулы памяти для разных типов данных:
| Пул памяти | Цели использования |
| wt_cache (40GB) | Затрагивает кэш WiredTiger для данных и индексов. Долгоживущие страницы (обычно 4–32 КБ). Из-за длительного времени жизни и редких освобождений артефакты из этой зоны достать сложнее — они редко переиспользуются для чужих запросов. Но если уязвимость позволяет читать освобождённые страницы кэша, можно извлечь чужие документы. |
| networking (2GB) | Затрагивает буферы сетевых соединений. Каждый входящий запрос (до 16 МБ по протоколу MongoDB) выделяет буфер под сообщение, заголовки, BSON-документы. Буферы живут миллисекунды: выделили → прочитали → обработали → освободили. Из-за активного переиспользования аллокатором (tcache/арены) один и тот же физический блок памяти может за секунду побывать у разных клиентов. Атакующий может: 1) отправить запрос с секретом, 2) дождаться free(), 3) вызвать выделение нового буфера того же размера и прочитать чужой пароль/токен. Размер 2 ГБ — это лимит на все одновременные соединения, но реально опасны именно маленькие быстрые буферы (сотни байт — килобайты). Здесь и возникает уязвимость. |
| journal (1GB) | Затрагивает журнал упреждающей записи (WAL) для durability. Пишется последовательно, обычно не переиспользуется в куче процесса. Освобождённые блоки из журнала редко попадают в общие пулы аллокатора, поэтому утечки через журнал маловероятны, если только уязвимость не даёт прямой доступ к файлам журнала (но это другая история). |
| oplog (500MB) | Oplog — capped-коллекция для репликации. Хранится как обычные данные в WiredTiger, не в динамической куче. Освобождений памяти в привычном смысле (malloc/free) там почти нет. Не является прямым источником артефактов для UAF/неинициализированного чтения. |
| session (200MB) | Содержит сессии пользователей (логины, временные данные, транзакции). Здесь могут быть живые токены, но сессии живут относительно долго (минуты-часы) и не освобождаются постоянно. Потенциально интересно, если уязвимость позволяет читать уже выделенную чужую память (не освобождённую). Однако классический UAF требует именно free() → reuse, а сессии редко free() до истечения таймаута. |
Credentials находятся в networking/session пулах, но уязвимость читает только networking pool, что и создаёт критический разрыв между теоретическим доступом к памяти и практическим извлечением значимых данных. Даже если пароль был в памяти, он мог быть в session-пуле, а эксплойт читает из networking-пула.
MongoBleed — это как попытка выловить единственную прозрачную рыбу, которая на 0.1 секунды появляется в случайной точке Чёрного моря, используя слепые забросы вчерашней сети с дырками, при этом 95% времени вы будете вытаскивать только старые водоросли и мусор. Разобранный код ранее – это скорее «proof-of-concept», чем «weaponized exploit».
Тестирование уязвимости
В рамках тестирования был собран стенд с уязвимой версией MongoDB 8.2.2 и предустановленный библиотекой Zlib. Стенд обрабатывал учётные данные при аутентификации, задействуя при этом саму MongoDB, что делало условия более приближенными к реальным. Нагрузка в виде частой аутентификации на стенд принципиально повышает шансы на получение учетных данных через уязвимость MongoBleed из-за особенности работы памяти, аллокатора и логики самого эксплойта.
В ходе проведения тестирования инструмент помогал выявить детали подключения, UUID, внутреннюю адресацию. Во всех случаях эксплуатации не наблюдались данные аутентификации, включающие обещанные токены, пароли или ещё что-то подобное.

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

Это обусловлено тем, что эта уязвимость более похожа на game of chance. Атакующий надеется, что неинициализированная память содержит ценные данные и что парсер BSON выдаст их как читаемую строку. К тому же она возвращает неструктурированные, случайные фрагменты данных, захват ценной информации требует от атакующего тысячи запросов. Это делает эксплуатацию «шумной» — легко обнаруживаемой системами мониторинга.
Таким образом, результаты тестирования полностью соответствуют реальному поведению уязвимости в условиях, приближенных к боевым, и подтверждают, что для успешной эксплуатации необходим не только доступ к сети, но и терпение для проведения тысяч запросов, а также элемент удачи. Это делает эксплуатацию уязвимости малоэффективной, что и было подтверждено в рамках тестирования.
Инцидент со взломом серверов Rainbow Six Siege
Одним из громких событий, где, по предположениям сторонних исследователей, фигурирует данная CVE, был взлом серверов шутера Rainbow Six Siege.

27 декабря 2025 года серверы Rainbow Six Siege подверглись крупному взлому, в ходе которого злоумышленники смогли манипулировать внутренними системами для выдачи миллиардов внутриигровой валюты (R6 Credits), очков Renown, косметических предметов и эксклюзивных предметов на аккаунты игроков. Также были зафиксированы ложные блокировки и другие несанкционированные действия с аккаунтами.

Ubisoft отреагировала на атаку и отключила игру и внутриигровой Marketplace и объявила об откате всех затронутых транзакций.
В ходе расследования инцидента появляются неподтвержденные сообщения (по данным исследователей из VX-Underground) об эксплуатации уязвимости MongoDBleed (CVE-2025-14847) для извлечения учетных данных, что позволило злоумышленникам получить доступ к высокопривилегированным внутренним сервисам. Эксперты также заявляют, что сервера подвергались атакам сразу несколькими группировками злоумышленников.
Пока не удалось подтвердить или опровергнуть ни одно из этих заявлений: эксплуатацию бага в MongoDB, доступ к исходному коду компании, кражу пользовательских данных.
Ubisoft подтвердила факт манипуляций внутриигровыми системами Rainbow Six Siege. Никаких публичных доказательств более масштабной атаки пока не было.
Рекомендации по защите
- Следует обновить сервис MongoDB до безопасной или более поздней версии (Официальное руководство по обновлению MongoDB).
- Если сжатие Zlib не требуется, отключите его в конфигурации (Официальное руководство по защите Zlib).
- Доступ к сервису MongoDB должен быть ограничен с внешней сети. База данных должна быть доступна только локально для самого приложения.
- Также можно отказаться от Zlib и пользоваться альтернативами (Snappy или zstd).
Заключение
MongoBleed представляет собой довольно опасную уязвимость в системе обработки сжатых сообщений MongoDB. Основная проблема заключается в отсутствии должной проверки целостности данных между этапами декомпрессии и парсинга BSON-документов.
Проблема позволяет злоумышленнику получать фрагменты памяти сервера без необходимости предварительной аутентификации или специальных привилегий. Достаточно наличия сетевого доступа к порту MongoDB. Это делает уязвимость особенно опасной для публично доступных экземпляров базы данных.
Тем не менее, несмотря на впечатляющий теоретический потенциал утечки памяти, описанной как MongoBleed, её практическая эксплуатация сталкивается с фундаментальными техническими ограничениями, которые существенно снижают реальную эффективность в извлечении структурированных секретов.
В отличие от идеализированного сценария, при реальной атаке злоумышленник получает не чистый дамп памяти с аккуратно разложенными credentials, а высокоэнтропийный «мусор» из буферов сжатия и сетевых пулов.