Featured image of post CVE-2025-14847 - MongoBleed Analysis
RSS - Threats All RSS Feeds Share

CVE-2025-14847 - MongoBleed Analysis

Analysis of CVE-2025-14847 (MongoBleed), a pre-auth memory disclosure vulnerability in MongoDB, including technical details, threat activity, and mitigation guidance.

Table of Contents

  1. BLUF (Bottom Line Up Front)
  2. Executive Summary
  3. Key Findings
  4. Vulnerability Metrics
  5. Timeline
  6. Affected Versions
  7. Recommendations
  8. Mitigations
  9. Root Cause Analysis
  10. MITRE ATT&CK Mapping
  11. Threat Activity
  12. Detection
  13. References
  14. Public PoCs

BLUF (Bottom Line Up Front)

UPDATE 29.12.2025: CISA added CVE-2025-14847 to its Known Exploited Vulnerabilities (KEV) catalogue.

CVE-2025-14847 lets an unauthenticated attacker read heap memory - credentials, tokens, private keys, adjacent connection data - from any MongoDB instance that has network compression enabled. It is a length-handling bug in the compressed wire-protocol path, structurally the same class of defect as Heartbleed: the server trusts an attacker-declared length and returns the difference from memory. Public PoCs exist and we observe scanning against port 27017. Patch to a fixed version; if you cannot patch, drop zlib from the negotiated compressors. Treat credentials on any internet-facing instance as exposed. MongoDB Atlas is not affected - MongoDB patched all Atlas deployments before public disclosure.


Executive Summary

MongoBleed (CVE-2025-14847) is a pre-authentication heap over-read in MongoDB’s decompression of OP_COMPRESSED wire-protocol messages. When compression is negotiated, the server sizes the decompressed message from an attacker-controlled length field but never verifies how many bytes the decompressor actually produced. The unwritten remainder of the buffer - uninitialized heap - is parsed as part of the message and can be reflected back to the attacker.

No authentication is required: compression sits below the command layer, so any peer that has completed the initial hello handshake can send compressed frames. Repeated requests let an attacker walk adjacent heap and reassemble secrets across many reads.

Risk: High for any internet-exposed MongoDB that offers zlib compression, which a default server configuration does - the attacker negotiates it as the connecting client, so legitimate driver settings are irrelevant. Shodan shows roughly 200,000 exposed instances. MongoDB Atlas is not affected - MongoDB patched all Atlas deployments before public disclosure.

Immediate action: Patch to a fixed version or drop zlib from the negotiated compressors; restrict internet exposure; enforce authentication and TLS; monitor for anomalous OP_COMPRESSED traffic on port 27017.


Key Findings

  • Pre-auth exploitation: No credentials required; a peer that completed the hello handshake can send crafted OP_COMPRESSED frames to TCP/27017.
  • Root cause: A length parameter inconsistency (CWE-130) - the message length is taken from the attacker’s declared uncompressedSize rather than the number of bytes the decompressor actually wrote.
  • Wide version impact: Affects MongoDB 3.6 through 8.2 with network compression enabled.
  • Public PoCs: 8+ proof-of-concept scripts are published on GitHub (as of 28.12.2025).
  • Exposure: Shodan shows roughly 200,000 MongoDB instances reachable from the internet.
  • No ransomware attribution yet: We have not linked any ransomware victim to this CVE. The disclosure itself yields no code execution - its value to an attacker is the recovered secrets.

Vulnerability Metrics

MetricValue
CVECVE-2025-14847
CWECWE-130 - Improper Handling of Length Parameter Inconsistency
CVSS 4.08.7 (High)
CVSS 4.0 VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N
CVSS 3.17.5 (High)
CVSS 3.1 VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
EPSS0.98
CISA KEVListed (Dec 29, 2025)
Vendor AdvisorySERVER-115508

Timeline

DateEvent
Dec 15, 2025MongoDB releases security advisory
Dec 19, 2025NVD publishes CVE-2025-14847
Dec 23-27, 2025Release of multiple PoCs on GitHub

Affected Versions

BranchVulnerableFixed
8.2< 8.2.38.2.3+
8.0< 8.0.178.0.17+
7.0< 7.0.287.0.28+
6.0< 6.0.276.0.27+
5.0< 5.0.325.0.32+
4.4< 4.4.304.4.30+
4.2All versionsNo patch available
4.0All versionsNo patch available
3.6All versionsNo patch available

MongoDB Atlas: Not affected. MongoDB patched all Atlas deployments before public disclosure. Only self-managed installations require action.


Recommendations

Assume in-memory secrets were leaked. Rotate database users, application secrets, and session tokens. Review egress logs for data staging to unfamiliar hosts.

  1. Inventory all MongoDB instances including development, staging, production, and test environments.
  2. Prioritise patching by internet exposure and data sensitivity.
  3. Drop zlib from the compressor list if you cannot patch immediately.
  4. Assume credential exposure on any unpatched, reachable instance.
  5. Rotate secrets - database users, application secrets, API tokens, session keys.
  6. Review network logs for unusual traffic to port 27017 from unfamiliar sources.

Mitigations

  • Remove direct internet exposure.
  • Enforce IP allowlists, VPN, or privileged network segments.
  • Require SCRAM authentication and TLS for all connections.

Patching

Upgrade to a fixed version:

  • MongoDB 8.2.3+
  • MongoDB 8.0.17+
  • MongoDB 7.0.28+
  • MongoDB 6.0.27+
  • MongoDB 5.0.32+
  • MongoDB 4.4.30+

Temporary Workaround

Only the zlib path is vulnerable, so removing zlib from the negotiated compressors makes decompressData unreachable. Keep snappy and zstd if you rely on compression:

1
2
3
net:
  compression:
    compressors: "snappy,zstd"   # zlib omitted; use "none" to disable compression entirely

The change requires service restart.


Root Cause Analysis

MongoBleed is a heap over-read: the server returns more bytes than the attacker’s message legitimately contains, and the surplus is whatever happened to be adjacent in the process heap.

The bug lives in how MongoDB reconstructs a compressed wire-protocol message, on a path that runs before authentication is enforced. It is the same shape of defect as Heartbleed (CVE-2014-0160) - a declared length the code trusts without confirming it against the data actually present.

The MongoDB wire protocol

Every message on TCP/27017 begins with a 16-byte standard header:

1
2
3
4
5
6
struct MsgHeader {
  int32 messageLength;  // total size, including this header
  int32 requestID;
  int32 responseTo;
  int32 opCode;         // operation type
};

To save bandwidth, client and server negotiate compression during the initial hello handshake. Once negotiated, either side may wrap any message in an OP_COMPRESSED envelope (opcode 2012):

1
2
3
4
5
6
7
struct OP_COMPRESSED {
  MsgHeader header;           // opCode = 2012
  int32     originalOpcode;   // the wrapped message's real opcode
  int32     uncompressedSize; // size of the payload AFTER decompression
  uint8     compressorId;     // 0 = noop, 1 = snappy, 2 = zlib, 3 = zstd
  uint8     compressedData[]; // the compressed body
};

Two fields matter for the bug: uncompressedSize and compressorId. Both are fully attacker-controlled, and both are read before any authentication check - compression sits underneath the command layer, so an unauthenticated peer that has completed the hello handshake can already send OP_COMPRESSED frames.

The decompression path

When the server receives an OP_COMPRESSED frame with compressorId = 2 (zlib), it turns the compressed body back into a normal message. Conceptually:

  1. Read uncompressedSize from the frame.
  2. Allocate an output buffer of that size.
  3. Call the zlib compressor to decompress compressedData into that buffer, and take back the number of bytes it produced.
  4. Treat the buffer, for that produced length, as a normal uncompressed message and hand it to the command dispatcher.

The defect is in step 3’s bookkeeping: the zlib decompressor reports the size of the buffer it was handed, not the amount of data it actually wrote into it.

The bug: returning the buffer size instead of the decompressed size

The zlib path lives in ZlibMessageCompressor::decompressData (src/mongo/transport/message_compressor_zlib.cpp). Here is the vulnerable function; the patch differed by the single return line, shown below it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
StatusWith<std::size_t> ZlibMessageCompressor::decompressData(ConstDataRange input,
                                                              DataRange output) {
    // ... RFC1950 header validation (compression method, FCHECK) ...

    uLongf length = output.length();
    int ret = ::uncompress(const_cast<Bytef*>(reinterpret_cast<const Bytef*>(output.data())),
                           &length,
                           reinterpret_cast<const Bytef*>(input.data()),
                           input.length());

    if (ret != Z_OK) {
        return Status{ErrorCodes::BadValue, "Compressed message was invalid or corrupted"};
    }

    counterHitDecompress(input.length(), length);
    return {output.length()};
}

length is one variable doing two jobs. Going in, it holds the capacity of output - a buffer sized to the attacker’s uncompressedSize. zlib treats that argument (destLen) as in/out: on a Z_OK return it has overwritten length with the number of bytes it actually wrote. So after the call, length is the true decompressed size while output.length() is still the full allocated capacity. The two diverge exactly when the compressed body inflates to less than the declared size.

The patch is one line - which of those two the function returns:

1
2
-    return {output.length()};
+    return {length};

That single substitution is the whole vulnerability. A decompressor’s return value is how much data the caller believes the message contains. By returning output.length(), the function reports the full attacker-declared size even though zlib wrote only a fraction of the buffer.

Why heap leaks (CWE-130)

Take an attacker frame with:

  • uncompressedSize = 60000, so output is a 60000-byte buffer
  • a small but well-formed zlib body that inflates to ~40 bytes

::uncompress() writes 40 bytes into the front of the buffer, sets length = 40, and returns Z_OK. It does not touch the remaining ~59960 bytes - zlib only guards against overflowing the buffer, it never zeroes the tail. Those bytes are whatever the allocator last left there: fragments of other connections’ buffers, command arguments, authentication material, session data, pointers.

The vulnerable return {output.length()} then tells the caller the message is 60000 bytes long. The transport layer forwards all 60000 - 40 bytes of real content plus 59960 bytes of uninitialized heap - into the message pipeline, and the surplus is surfaced back to the attacker in the reply. Vary uncompressedSize across requests and an attacker walks adjacent heap, reassembling secrets over many reads. This is exactly Heartbleed’s primitive: ask for more than you sent, receive the difference from memory. Hence MongoBleed.

The fix (SERVER-115508)

The patch (commit 505b660) changes return {output.length()} to return {length} - the decompressor now reports the bytes zlib actually produced, so no uninitialized tail is ever forwarded. The snappy and zstd compressors already returned their true output length; only the zlib path carried the defect, which is why omitting zlib specifically is a valid workaround.

Why zlib is the exposure

The bug is reachable whenever the server will accept a zlib-compressed frame, and that is a lower bar than it sounds. A MongoDB server advertises all three algorithms - snappy, zstd, and zlib - in its default net.compression.compressors set, and in this attack the adversary is the client. Compression is negotiated per connection by whoever connects, so an unauthenticated peer can simply pick zlib during the hello handshake - even if none of your legitimate drivers use compression at all. Removing zlib from net.compression.compressors is therefore a complete workaround: the server stops offering it, ZlibMessageCompressor::decompressData is never called, and snappy and zstd keep working. MongoDB Atlas was patched ahead of disclosure, so managed Atlas clusters were never exposed.


MITRE ATT&CK Mapping

TechniqueIDDescription
Exploit Public-Facing ApplicationT1190Attackers exploit MongoDB instances exposed to the internet
Unsecured CredentialsT1552Memory disclosure leaks credentials from the server heap
Network Service DiscoveryT1046Mass scanning for port 27017

Threat Activity

Exploitation Status

Public PoCs are available on GitHub, and our sensors record scanning against port 27017. The vulnerability is pre-authentication and requires a single crafted message, which lowers the barrier to opportunistic use.

Internet Exposure (Shodan)

MetricValue
Exposed MongoDB instances201,887
Top countriesUSA, Netherlands, China, Germany
Vulnerable versions observed8.0.15, 7.0.5, 5.0.28, 4.0.9

Baysec Honeypot Data (24h Snapshot)

In 24-hour window the honeypot logged 58 exploitation attempts on port 27017.

Ransomware

As of this writing we have not linked any ransomware victim to CVE-2025-14847. Memory disclosure on its own does not grant code execution; its value to an attacker is the credentials, keys, and tokens recovered from heap, which can enable follow-on access.


Detection

  • Spikes in OP_COMPRESSED traffic to port 27017 from unfamiliar IPs.
  • Large response payloads returned to small compressed requests - the signature of an over-read.
  • Repeated compressed messages from one source with varying declared sizes.
  • Scanning activity from new ASNs targeting 27017.

References

Official Advisory

Background


Public PoCs

Warning: Verify any PoC before running it. Threat actors distribute malware through fake exploit repositories, targeting researchers with lures for recent CVEs. In December 2025 we observed campaigns distributing Webrat RAT through weaponised PoC repos. Review code manually, run in isolated environments, and cross-reference with trusted sources. See our December 2025 Threats Summary.