Skip to content

Mongodb Aux Modules - #21785

Open
h00die wants to merge 19 commits into
rapid7:masterfrom
h00die:mongodb_schemadump
Open

Mongodb Aux Modules#21785
h00die wants to merge 19 commits into
rapid7:masterfrom
h00die:mongodb_schemadump

Conversation

@h00die

@h00die h00die commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Description

MongoDB is sorely missing capabilities. mysql/mssql/postgres have hash dumpers, login, version, schema dumping, lots of fun stuff. However, nobody seems to want to give Mongo some <3 except Gregory Man, who is the man.

This PR adds:

  1. Adds: mongodb_version scanner
  2. Updates: mongodb_login to work on >=3.0 mongodb (new auth mechanisms)
  3. Adds: mongodb_schemadump
  4. Adds: mongodb_hashdump

Notably this is not a full new hash capability since thats a long and arduous process, this just adds the dumping capabilities for the time being (theyre in hashcat format)

Reviewer Notes

There are several modules in this PR, and while thats not typical and can be a bit of a pain, the version portion is part of login, the login portion is part of schema and hash dump, so it felt all similar.

Verification Steps

See module docs for each module

AI Usage Disclosure

Gemini assisted in making schemadump, and updating login. GLM-4.7 assisted wtih version and hashdump. I did the verification, removed extraneous things, made them work more how MSF users would think they work, and wrote all the docs (therefore, theyre poorer quality than AI would make, but at least the Scenarios sections are legit)

@h00die

h00die commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

incorporated the changes from #21788 and credited @prithvee07 . Retested and looking good.

@h00die

h00die commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

meterp acceptance build fail is unrelated.

@bwatters-r7

Copy link
Copy Markdown
Contributor

Not to be greedy or anything, but is there any chance for some rspecs for that library? 🤣

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR expands Metasploit’s MongoDB auxiliary scanner capabilities by adding new MongoDB scanners (version, schema dump, hash dump) and updating the existing MongoDB login module to support SCRAM authentication via a new shared wire-protocol mixin.

Changes:

  • Added new MongoDB auxiliary scanner modules for version detection, schema enumeration, and hash extraction.
  • Updated mongodb_login to support SCRAM-SHA-1 (MongoDB 3.0+) with a MONGODB-CR fallback.
  • Introduced a shared Msf::Exploit::Remote::Mongodb mixin for OP_QUERY packet construction and BSON reply parsing.

Impact Analysis:

  • Blast radius: medium — affects all MongoDB scanner modules in this PR plus any future modules that include Msf::Exploit::Remote::Mongodb; downstream impact beyond MongoDB tooling is low.
  • Data and contract effects: adds new credential/hash entries to the Metasploit database via create_credential; no schema migrations or external API contracts changed.
  • Rollback and test focus: rollback is straightforward (remove new modules/mixin), but test focus should include auth detection behavior (mongodb_have_auth_error?), SCRAM login success/failure paths, and hash/schema dump completeness.
File summaries
File Description
modules/auxiliary/scanner/mongodb/mongodb_version.rb New scanner to query buildInfo and report MongoDB version.
modules/auxiliary/scanner/mongodb/mongodb_schemadump.rb New schema enumerator that lists DBs/collections and samples documents into JSON loot.
modules/auxiliary/scanner/mongodb/mongodb_login.rb Adds SCRAM-SHA-1 login support with legacy fallback; uses new wire-protocol mixin.
modules/auxiliary/scanner/mongodb/mongodb_hashdump.rb New hash extraction module for system/app user hashes with credential storage.
lib/msf/core/exploit/remote/mongodb.rb New shared wire-protocol helper mixin for MongoDB OP_QUERY + BSON parsing.
documentation/modules/auxiliary/scanner/mongodb/mongodb_version.md Documentation for the new version scanner module.
documentation/modules/auxiliary/scanner/mongodb/mongodb_schemadump.md Documentation for the new schema dump module (including scenarios).
documentation/modules/auxiliary/scanner/mongodb/mongodb_login.md Documentation for updated login behavior (SCRAM + fallback).
documentation/modules/auxiliary/scanner/mongodb/mongodb_hashdump.md Documentation for the new hash dump module and hashcat usage.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 6
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread lib/msf/core/exploit/remote/mongodb.rb
Comment thread modules/auxiliary/scanner/mongodb/mongodb_hashdump.rb Outdated
Comment thread modules/auxiliary/scanner/mongodb/mongodb_schemadump.rb
Comment thread documentation/modules/auxiliary/scanner/mongodb/mongodb_login.md
Comment thread documentation/modules/auxiliary/scanner/mongodb/mongodb_schemadump.md Outdated
Comment thread lib/msf/core/exploit/remote/mongodb.rb
Comment thread modules/auxiliary/scanner/mongodb/mongodb_hashdump.rb Outdated
Comment thread modules/auxiliary/scanner/mongodb/mongodb_hashdump.rb Outdated
Comment thread modules/auxiliary/scanner/mongodb/mongodb_hashdump.rb Outdated
Comment thread modules/auxiliary/scanner/mongodb/mongodb_login.rb Outdated
Comment thread modules/auxiliary/scanner/mongodb/mongodb_login.rb Outdated
Comment thread modules/auxiliary/scanner/mongodb/mongodb_schemadump.rb Outdated
Comment thread modules/auxiliary/scanner/mongodb/mongodb_hashdump.rb
Comment thread modules/auxiliary/scanner/mongodb/mongodb_login.rb
h00die and others added 2 commits September 2, 2026 16:37
Co-authored-by: Brendan <bwatters@rapid7.com>
Co-authored-by: Brendan <bwatters@rapid7.com>
@bwatters-r7

bwatters-r7 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

#21785 (comment)
That change probably requires some wiring in the rest of the code?
I also just noticed there are some double prints thanks to the scanner mixin.....
modules/auxiliary/scanner/mongodb/mongodb_login.rb:221, /home/
modules/auxiliary/scanner/mongodb/mongodb_hashdump.rb:69, and modules/auxiliary/scanner/mongodb/mongodb_version.rb:48

@h00die

h00die commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

yup, i'll get to it later, wanted it bound in though to test against later

@h00die

h00die commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Pulled auth out of the modules and put it in the centralized lib. That's where it should be and helped standardized some options, pull duplicated code out of modules, and such. I didn't know mongo allowed you to login to a db and not just the system in general. so i've now tested that, and also fixed a bug in schemadump where you can't list the databases, but you provide a database name

@prithvee07

Copy link
Copy Markdown

diff --git a/spec/lib/msf/core/exploit/remote/mongodb_spec.rb b/spec/lib/msf/core/exploit/remote/mongodb_spec.rb
index 0c730bf15..1eeafe9b0 100644
--- a/spec/lib/msf/core/exploit/remote/mongodb_spec.rb
+++ b/spec/lib/msf/core/exploit/remote/mongodb_spec.rb
@@ -584,4 +584,145 @@ RSpec.describe Msf::Exploit::Remote::Mongodb do
expect(subject.mongodb_query_all(sock, 'test.system.users', query)).to eq([])
end
end
+

  • describe '#authenticate' do
  • let(:user) { 'admin' }
  • let(:pass) { 's3cr3t' }
  • let(:db) { 'admin' }
  • before do
  •  subject.datastore['USERNAME'] = user
    
  •  subject.datastore['PASSWORD'] = pass
    
  •  subject.datastore['AUTH_DB'] = db
    
  • end
  • context 'SCRAM-SHA-1 (MongoDB 3.0+)' do
  •  let(:server_r) { 'fixedServerNonceValue1234567890' }
    
  •  let(:salt_bytes) { 'abcdefgh12345678' }
    
  •  let(:iterations) { 10_000 }
    
  •  let(:server_first_message) { "r=#{server_r},s=#{Rex::Text.encode_base64(salt_bytes)},i=#{iterations}" }
    
  •  let(:sasl_start_reply) do
    
  •    op_reply({ ok: 1, conversationId: 1, payload: BSON::Binary.new(server_first_message) })
    
  •  end
    
  •  it 'authenticates and sends an RFC 5802-correct client proof computed from the server salt/iterations' do
    
  •    sasl_continue_reply = op_reply({ ok: 1, done: true })
    
  •    sock = FakeSock.new([sasl_start_reply, sasl_continue_reply])
    
  •    allow(subject).to receive(:sock).and_return(sock)
    
  •    expect(subject.authenticate).to eq('SCRAM-SHA-1')
    
  •    expect(sock.sent.length).to eq(2)
    
  •    # Recover the client nonce the mixin generated from what it actually
    
  •    # put on the wire, then independently recompute the expected proof
    
  •    # per RFC 5802 (not by calling the mixin's own private helpers) and
    
  •    # check the second message matches exactly.
    
  •    _, first_cmd = parse_op_query(sock.sent[0])
    
  •    client_first_message = first_cmd['payload'].data
    
  •    client_nonce = client_first_message[/r=([^,]+)/, 1]
    
  •    scram_user = client_first_message[/n=([^,]+),r=/, 1]
    
  •    expect(scram_user).to eq(user)
    
  •    expect(client_nonce.length).to eq(24)
    
  •    client_first_bare = "n=#{scram_user},r=#{client_nonce}"
    
  •    digest_pass = Digest::MD5.hexdigest("#{user}:mongo:#{pass}")
    
  •    salted_password = OpenSSL::PKCS5.pbkdf2_hmac(digest_pass, salt_bytes, iterations, 20, OpenSSL::Digest.new('SHA1'))
    
  •    client_key = OpenSSL::HMAC.digest('sha1', salted_password, 'Client Key')
    
  •    stored_key = OpenSSL::Digest::SHA1.digest(client_key)
    
  •    client_final_without_proof = "c=biws,r=#{server_r}"
    
  •    auth_message = "#{client_first_bare},#{server_first_message},#{client_final_without_proof}"
    
  •    client_signature = OpenSSL::HMAC.digest('sha1', stored_key, auth_message)
    
  •    client_proof = Rex::Text.xor(client_key, client_signature)
    
  •    expected_final_message = "#{client_final_without_proof},p=#{Rex::Text.encode_base64(client_proof)}"
    
  •    _, second_cmd = parse_op_query(sock.sent[1])
    
  •    expect(second_cmd['payload'].data).to eq(expected_final_message)
    
  •    expect(second_cmd['conversationId']).to eq(1)
    
  •  end
    
  •  it 'sends an additional empty saslContinue when the server leaves the conversation open (done: false)' do
    
  •    interim_reply = op_reply({ ok: 1, done: false })
    
  •    final_reply = op_reply({ ok: 1, done: true })
    
  •    sock = FakeSock.new([sasl_start_reply, interim_reply, final_reply])
    
  •    allow(subject).to receive(:sock).and_return(sock)
    
  •    expect(subject.authenticate).to eq('SCRAM-SHA-1')
    
  •    expect(sock.sent.length).to eq(3)
    
  •    _, third_cmd = parse_op_query(sock.sent[2])
    
  •    expect(third_cmd['payload'].data).to eq('')
    
  •    expect(third_cmd['conversationId']).to eq(1)
    
  •  end
    
  •  it 'falls back to MONGODB-CR when the server rejects saslContinue (bad credentials)' do
    
  •    sasl_continue_fail_reply = op_reply({ ok: 0, errmsg: 'Authentication failed.', code: 18 })
    
  •    getnonce_reply = op_reply({ ok: 1, nonce: 'abcNonce' })
    
  •    cr_auth_reply = op_reply({ ok: 1 })
    
  •    sock = FakeSock.new([sasl_start_reply, sasl_continue_fail_reply, getnonce_reply, cr_auth_reply])
    
  •    allow(subject).to receive(:sock).and_return(sock)
    
  •    expect(subject.authenticate).to eq('MONGODB-CR')
    
  •    expect(sock.sent.length).to eq(4)
    
  •  end
    
  • end
  • context 'MONGODB-CR fallback (MongoDB < 3.0, saslStart unsupported)' do
  •  it 'authenticates via getnonce + authenticate with the correct MD5 key digest' do
    
  •    nonce = 'abc123nonce'
    
  •    sasl_fail_reply = op_reply({ ok: 0, errmsg: 'no such command: saslStart', code: 59 })
    
  •    getnonce_reply = op_reply({ ok: 1, nonce: nonce })
    
  •    cr_auth_reply = op_reply({ ok: 1 })
    
  •    sock = FakeSock.new([sasl_fail_reply, getnonce_reply, cr_auth_reply])
    
  •    allow(subject).to receive(:sock).and_return(sock)
    
  •    expect(subject.authenticate).to eq('MONGODB-CR')
    
  •    expect(sock.sent.length).to eq(3)
    
  •    _, cr_cmd = parse_op_query(sock.sent[2])
    
  •    expected_key = Digest::MD5.hexdigest(nonce + user + Digest::MD5.hexdigest("#{user}:mongo:#{pass}"))
    
  •    expect(cr_cmd['key']).to eq(expected_key)
    
  •    expect(cr_cmd['user']).to eq(user)
    
  •    expect(cr_cmd['nonce']).to eq(nonce)
    
  •  end
    
  • end
  • it 'returns nil when neither SCRAM-SHA-1 nor MONGODB-CR succeed' do
  •  sasl_fail_reply = op_reply({ ok: 0, errmsg: 'no such command: saslStart', code: 59 })
    
  •  getnonce_reply = op_reply({ ok: 0, errmsg: "no such cmd: 'getnonce'", code: 59 })
    
  •  sock = FakeSock.new([sasl_fail_reply, getnonce_reply])
    
  •  allow(subject).to receive(:sock).and_return(sock)
    
  •  expect(subject.authenticate).to be_nil
    
  •  expect(sock.sent.length).to eq(2)
    
  • end
  • it 'skips SCRAM but still attempts the CR fallback for a username containing a NUL byte' do
  •  subject.datastore['USERNAME'] = "bad\x00user"
    
  •  getnonce_reply = op_reply({ ok: 0 })
    
  •  sock = FakeSock.new([getnonce_reply])
    
  •  allow(subject).to receive(:sock).and_return(sock)
    
  •  expect(subject.authenticate).to be_nil
    
  •  expect(sock.sent.length).to eq(1) # no SCRAM packet sent, only getnonce
    
  • end
  • end
  • describe '#require_auth?' do
  • it 'returns true when listDatabases requires authentication' do
  •  response = op_reply({ ok: 0, errmsg: 'command listDatabases requires authentication', code: 13 })
    
  •  sock = FakeSock.new([response])
    
  •  allow(subject).to receive(:sock).and_return(sock)
    
  •  expect(subject.require_auth?).to be true
    
  • end
  • it 'returns false when listDatabases succeeds unauthenticated' do
  •  response = op_reply({ ok: 1, databases: [{ name: 'admin' }] })
    
  •  sock = FakeSock.new([response])
    
  •  allow(subject).to receive(:sock).and_return(sock)
    
  •  expect(subject.require_auth?).to be false
    
  • end
  • end
    end

@h00die

h00die commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

@prithvee07 can you mark blocks of code with triple ticks so its readable? Also, as previously noted, you should put that in a PR to my repo's mongodb_schemadump so I can look over the diff easily and you get credit. You should be able to ask claude how to do it, or have it do it for you. Just point it to this comment.

@bwatters-r7

Copy link
Copy Markdown
Contributor

@prithvee07, github makes collaboration easy, but you need to use it correctly. @h00die's already spent time copying over code that would have been trivial to add if you had PR'd to his branch, and now you have left a huge comment that should be a PR to h00die's branch.
We are thankful for each of our contributors, and we appreciate and respect the time everyone puts in to make Metasploit awesome, and we expect contributors to also value other contributors' time. Sending code in like this is not respecting h00die's time.
By not using github correctly, you are making @h00die spend more time than they should have to; please PR this to @h00die's branch rather than dropping it in comments or at the very least, use github suggestions so h00die can add the changes quickly. If you don't know how (all of us were once github beginners once) we are happy to help you, just ask.

@prithvee07

Copy link
Copy Markdown

@bwatters-r7 and @h00die i'm new to git and this my first project i am contributing

@h00die

h00die commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

@bwatters-r7 and @h00die i'm new to git and this my first project i am contributing

We understand, we've all been there. However, we're trying to give you clear instructions on how to do things the right way, and offering additional help in slack to guide you w/o having these long public discussions that end up in people's email boxes.

[*] 127.0.0.1:27017 - DB 'intranet' Collections: users, config
[*] 127.0.0.1:27017 - DB 'local' Collections: startup_log
[+] 127.0.0.1:27017 - Schema dumped to loot: /home/h00die/.msf4/loot/20260814091146_default_127.0.0.1_mongodb.schema_850455.json
[*] 127.0.0.1:27017 - Scanned 1 of 1 hosts (100% complete)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MongoDB 3.6 with NO Authentication

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rayanrayan145a@gmail

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

5 participants