// HackTricks · Network Services

27017,27018 - Pentesting MongoDB

27017,27018 - Pentesting MongoDB

Basic Information

MongoDB is an open-source, document-oriented database. A normal mongod or mongos listener defaults to TCP 27017, a shard server defaults to 27018, and a config server defaults to 27019.[7]

PORT      STATE SERVICE VERSION
27017/tcp open  mongodb MongoDB 2.6.9 2.6.9

Enumeration

Manual

from pymongo import MongoClient
client = MongoClient(host, port, username=username, password=password)
client.server_info() #Basic info
#If you have admin access you can obtain more info
admin = client.admin
admin_info = admin.command("serverStatus")
cursor = client.list_databases()
for db in cursor:
    print(db)
    print(client[db["name"]].list_collection_names())
#If admin access, you could dump the database also

Some MongoDB commands:

show dbs
use <db>
show collections
db.<collection>.find()  #Dump the collection
db.<collection>.count() #Number of records of the collection
db.current.find({"username":"admin"})  #Find in current db the username admin

Automatic

nmap -sV --script "mongo* and default" -p 27017 <IP> #By default all the nmap mongo enumerate scripts are used

Shodan

  • All mongodb: "mongodb server information"
  • Search for full open mongodb servers: "mongodb server information" -"partially enabled"
  • Only partially enable auth: "mongodb server information" "partially enabled"

Login

Self-managed MongoDB defaults to security.authorization: disabled, but it also defaults to binding only to localhost. An externally reachable listener without authorization is therefore a dangerous configuration, not a safe Internet-facing default.[7] The admin database is the usual authentication database for administrative users.

mongo <HOST>
mongo <HOST>:<PORT>
mongo <HOST>:<PORT>/<DB>
mongo <database> -u <username> -p '<password>'

The nmap script: mongodb-brute will check if creds are needed.

nmap -n -sV --script mongodb-brute -p 27017 <ip>

Brute force

Look inside /opt/bitnami/mongodb/mongodb.conf to know if credentials are needed:

grep "noauth.*true" /opt/bitnami/mongodb/mongodb.conf | grep -v "^#" #Not needed
grep "auth.*true" /opt/bitnami/mongodb/mongodb.conf | grep -v "^#\|noauth" #Not needed

Mongo Objectid Predict

Example from here.[1]

MongoDB ObjectIds are 12-byte values, conventionally displayed as 24 hexadecimal characters:[8]

http://techidiocy.com/_id-objectid-in-mongodb/

For example, here’s how we can dissect an actual Object ID returned by an application: 5f2459ac9fa6dc2500314019

  1. 5f2459ac: a 4-byte Unix timestamp (1596217772, Friday, 31 July 2020 17:49:32 UTC)
  2. 9fa6dc2500: a 5-byte random value generated once per client-side process
  3. 314019: a 3-byte counter initialized to a random value

Older ObjectId layouts exposed separate machine and process identifiers; the current specification replaced those fields with one per-process random value. The timestamp changes once per second and the counter increments within a process, so IDs observed from the same generator in a narrow time window can still be partially predictable. This is not universal: drivers generate ObjectIds client-side, different processes use different random values, and applications may choose unrelated _id values.[1][8]

Given an observed ObjectId, mongo-objectid-predict generates nearby candidates for authorization testing.[9] Treat this as a targeted IDOR test rather than a general ObjectId breaker: it is most effective when the target IDs share the timestamp, process-random component, and adjacent counter values.

The upstream command exposes --counter-diff (how many adjacent counter values to explore), --per-counter (how many timestamp offsets to try for each counter), and --backward (generate earlier rather than later candidates). Its defaults are 20 and 60, producing roughly a thousand candidates; expand them only after establishing that a smaller authorized sample shares the generator component.[9]

./mongo-objectid-predict 5ae9b90a2c144b9def01ec37
./mongo-objectid-predict 5ae9b90a2c144b9def01ec37 --counter-diff 50 --per-counter 120
./mongo-objectid-predict 5ae9b90a2c144b9def01ec37 --backward

The repository currently contains Python 2-era code and describes the older machine-ID/process-ID terminology. Review or port it before use, and do not mistake generated candidates for evidence that a resource exists or that access is authorized.

Post

With authorized root access to a self-managed host, you can audit the effective configuration and, in a disposable recovery lab, start a local-only instance with authorization disabled. Current YAML configuration uses security.authorization: disabled; legacy noauth = true examples apply to old configuration formats. Do not expose that recovery listener beyond loopback.[7]

MongoBleed zlib Memory Disclosure (CVE-2025-14847)

A widespread unauthenticated memory disclosure (“MongoBleed”) impacts MongoDB 3.6–8.2 when the zlib network compressor is enabled. The OP_COMPRESSED header trusts an attacker-supplied uncompressedSize, so the server allocates a buffer of that size and copies it back into responses even though only a much smaller compressed payload was provided. The extra bytes are uninitialized heap data from other connections, /proc, or the WiredTiger cache. Attackers then omit the expected BSON \x00 terminator so MongoDB’s parser keeps scanning that oversized buffer until it finds a terminator, and the error response echoes both the malicious document and the scanned heap bytes pre-auth on TCP/27017.[2]

Exposure requirements & quick checks

  • Server version must be within the vulnerable ranges (3.6, 4.0, 4.2, 4.4.0–4.4.29, 5.0.0–5.0.31, 6.0.0–6.0.26, 7.0.0–7.0.27, 8.0.0–8.0.16, 8.2.0–8.2.2).[6]
  • net.compression.compressors or networkMessageCompressors must include zlib (default on many builds). Check it from the shell with:
db.adminCommand({getParameter: 1, networkMessageCompressors: 1})
  • The attacker only needs network access to the MongoDB port. No authentication is necessary.[3][4]

Exploitation & harvesting workflow

  1. Initiate the wire-protocol handshake advertising compressors:["zlib"] so the session uses zlib.
  2. Send OP_COMPRESSED frames whose declared uncompressedSize is far larger than the real decompressed payload to force oversized heap allocation full of old data.
  3. Craft the embedded BSON without a final \x00 so the parser walks past attacker-controlled data into the oversized buffer while looking for a terminator.
  4. MongoDB emits an error that includes the original message plus whatever heap bytes were scanned, leaking memory. Repeat with varying lengths/offsets to aggregate secrets (creds/API keys/session tokens), WiredTiger stats, and /proc artifacts.[2]

The public PoC automates the probing offsets and carving of the returned fragments:[5]

python3 mongobleed.py --host <target> --max-offset 50000 --output leaks.bin

Detection noise signal (high-rate connections)

The attack usually generates many short-lived requests. Watch for spikes of inbound connections to mongod/mongod.exe. Example XQL hunt (>500 connections/min per remote IP, excluding RFC1918/loopback/link-local/mcast/broadcast/reserved ranges by default):[2]

Cortex XQL high-velocity Mongo connections
// High-velocity inbound connections to mongod/mongod.exe (possible MongoBleed probing)

dataset = xdr_data
| filter event_type = ENUM.NETWORK
| filter lowercase(actor_process_image_name) in ("mongod", "mongod.exe")
| filter action_network_is_server = true
| filter action_remote_ip not in (null, "")
| filter incidr(action_remote_ip, "10.0.0.0/8") != true and
        incidr(action_remote_ip, "192.168.0.0/16") != true and
        incidr(action_remote_ip, "172.16.0.0/12") != true and
        incidr(action_remote_ip, "127.0.0.0/8") != true and
        incidr(action_remote_ip, "169.254.0.0/16") != true and
        incidr(action_remote_ip, "224.0.0.0/4") != true and
        incidr(action_remote_ip, "255.255.255.255/32") != true and
        incidr(action_remote_ip, "198.18.0.0/15") != true
| filter action_network_session_duration <= 5000
| bin _time span = 1m
| comp count(_time) as Counter by agent_hostname, action_remote_ip, _time
| filter Counter >= 500

References


☧ CPTS · ordered posts

Locked

Unlock with your Main_Key through local GPG.

Start the local GPG helper

In your cheatsheet checkout, run:

npm run unlock:helper

Open the link it prints, or paste its pairing code below. Keep the terminal open until GPG finishes.

Import an index you already decrypted

Download the encrypted index, then decrypt with GPG:

gpg --output report-index.json --decrypt report-index.gpg

Your private key stays in GPG. The decrypted index is saved in this browser so you can reopen it after navigation, refreshes and restarts. Lock this browser or clear this site's browser data to remove the saved index. Nothing is uploaded.