f8bf996233
author Hynek Petrak <hynek.petrak@gmail.com> 1595628792 +0200 committer Spencer McIntyre <Spencer_McIntyre@rapid7.com> 1598532753 -0400 Added module to dump hashes from LDAP added hash formatters, documentation, ldap authentication typo sanitizing added scenario for NASDeluxe added few hash attribute examples typo correction Co-authored-by: bcoles <bcoles@gmail.com> typo correction Co-authored-by: bcoles <bcoles@gmail.com> typo correction Co-authored-by: bcoles <bcoles@gmail.com> avoid option name conflicts added test scenario linted linted Dump all nameContexts, not just the first one. Search creds in multiple attributes. attemt to dump special and operational attributes check if ldap bind succeeded sanitize the ldap hashes, skip invalid, remove {crypt} prefix memory optimization for large LDAP servers spaces at eols put header to the ldif loot added other LDAP hash formats, don't save empty ldif, dump root DSE now we handle vmdir case too explictly set md5crypt for $ Converted to scanner to improve performance on large networks krbprincipalkey, memory optimization for ldap.search handle additional hash types be verbose about search errors added per host timeout catch exception from Net::Ldap shorten the param value handle pwdhistory entries added comment about sambapwdhistory value reject shorter empty sambapassordhistory entries reject null nt and lm hashes report assumed clear text passwords refactored timeout for the sake of the loot ignore {SASL} pass-trough auth entries distinguish unresolved hashes from clear passwords print ldap server error message, meaningful loot name correct exception handling handle hashes with eol remove debug line handle pkcs12 in binary form attemt to control timeout on bind operation leave LDAP#bind to be called implicitly in #search remove debug line fixed bug, when pillage broke the outer LDAP#search learning ruby monkey patched ldap connection handling, ignoring bind errors commenting the net:LDAP misbehaviour review fixes review fixes moving ldap.search into a function remove fail_with, store loot from one place, print statistics linting consolidated ldap_new and connect, don't catch exceptions in the mixin Complete the credential creation Co-authored-by: Spencer McIntyre <58950994+smcintyre-r7@users.noreply.github.com>
140 lines
3.5 KiB
Ruby
140 lines
3.5 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
#
|
|
# This mixin is a wrapper around Net::LDAP
|
|
#
|
|
|
|
require 'net-ldap'
|
|
|
|
module Msf
|
|
module Exploit::Remote::LDAP
|
|
def initialize(info = {})
|
|
super
|
|
|
|
register_options([
|
|
Opt::RHOST,
|
|
Opt::RPORT(389),
|
|
OptBool.new('SSL', [false, 'Enable SSL on the LDAP connection', false]),
|
|
OptString.new('BIND_DN', [false, 'The username to authenticate to LDAP server']),
|
|
OptString.new('BIND_PW', [false, 'Password for the BIND_DN'])
|
|
])
|
|
|
|
register_advanced_options([
|
|
OptFloat.new('LDAP::ConnectTimeout', [true, 'Timeout for LDAP connect', 10.0])
|
|
])
|
|
end
|
|
|
|
def rhost
|
|
datastore['RHOST']
|
|
end
|
|
|
|
def rport
|
|
datastore['RPORT']
|
|
end
|
|
|
|
def peer
|
|
"#{rhost}:#{rport}"
|
|
end
|
|
|
|
def get_connect_opts()
|
|
connect_opts = {
|
|
host: rhost,
|
|
port: rport,
|
|
connect_timeout: datastore['LDAP::ConnectTimeout']
|
|
}
|
|
|
|
if datastore['SSL']
|
|
connect_opts[:encryption] = {
|
|
method: :simple_tls,
|
|
tls_options: {
|
|
verify_mode: OpenSSL::SSL::VERIFY_NONE
|
|
}
|
|
}
|
|
end
|
|
|
|
if datastore['BIND_DN']
|
|
connect_opts[:auth] = {
|
|
method: :simple,
|
|
username: datastore['BIND_DN']
|
|
}
|
|
if datastore['BIND_PW']
|
|
connect_opts[:auth][:password] = datastore['BIND_PW']
|
|
end
|
|
end
|
|
connect_opts
|
|
end
|
|
|
|
def ldap_connect(opts = {}, &block)
|
|
Net::LDAP.open(get_connect_opts.merge(opts), &block)
|
|
end
|
|
|
|
def ldap_new(opts = {})
|
|
ldap = Net::LDAP.new(get_connect_opts.merge(opts))
|
|
|
|
# NASTY, but required
|
|
# monkey patch ldap object in order to ignore bind errors
|
|
# Some servers (e.g. OpenLDAP) return result even after a bind
|
|
# has failed, e.g. with LDAP_INAPPROPRIATE_AUTH - anonymous bind disallowed.
|
|
# See: https://www.openldap.org/doc/admin23/security.html#Authentication%20Methods
|
|
# "Note that disabling the anonymous bind mechanism does not prevent anonymous
|
|
# access to the directory."
|
|
#
|
|
# Bug created for Net:LDAP https://github.com/ruby-ldap/ruby-net-ldap/issues/375
|
|
#
|
|
def ldap.use_connection(args)
|
|
if @open_connection
|
|
yield @open_connection
|
|
else
|
|
begin
|
|
conn = new_connection
|
|
conn.bind(args[:auth] || @auth)
|
|
# Commented out vs. original
|
|
# result = conn.bind(args[:auth] || @auth)
|
|
# return result unless result.result_code == Net::LDAP::ResultCodeSuccess
|
|
yield conn
|
|
ensure
|
|
conn.close if conn
|
|
end
|
|
end
|
|
end
|
|
yield ldap
|
|
end
|
|
|
|
def get_naming_contexts(ldap)
|
|
vprint_status("#{peer} Getting root DSE")
|
|
|
|
unless (root_dse = ldap.search_root_dse)
|
|
print_error("#{peer} Could not retrieve root DSE")
|
|
return
|
|
end
|
|
|
|
vprint_line(root_dse.to_ldif)
|
|
|
|
naming_contexts = root_dse[:namingcontexts]
|
|
|
|
# NOTE: Net::LDAP converts attribute names to lowercase
|
|
if naming_contexts.empty?
|
|
print_error("#{peer} Empty namingContexts attribute")
|
|
return
|
|
end
|
|
|
|
naming_contexts
|
|
end
|
|
|
|
def discover_base_dn(ldap)
|
|
naming_contexts = get_naming_contexts(ldap)
|
|
|
|
unless naming_contexts
|
|
print_error("#{peer} Base DN cannot be determined")
|
|
return
|
|
end
|
|
|
|
# NOTE: We assume the first namingContexts value is the base DN
|
|
base_dn = naming_contexts.first
|
|
|
|
print_good("#{peer} Discovered base DN: #{base_dn}")
|
|
base_dn
|
|
end
|
|
end
|
|
end
|