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
|