# -*- 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