Module: Msf::Exploit::Remote::LDAP
- Includes:
- Metasploit::Framework::LDAP::Client, Kerberos::ServiceAuthenticator::Options, Kerberos::Ticket::Storage
- Included in:
- Metasploit::Framework::LoginScanner::LDAP
- Defined in:
- lib/msf/core/exploit/remote/ldap/server.rb,
lib/msf/core/exploit/remote/ldap.rb
Overview
This module exposes methods for querying a remote LDAP service
Defined Under Namespace
Modules: Server
Instance Method Summary collapse
-
#discover_base_dn(ldap) ⇒ String
Discover the base DN of the target LDAP server via the LDAP server's naming contexts.
-
#get_connect_opts ⇒ Hash
Set the various connection options to use when connecting to the target LDAP server based on the current datastore options.
-
#get_naming_contexts(ldap) ⇒ Net::BER::BerIdentifiedArray
Get the naming contexts for the target LDAP server.
-
#initialize(info = {}) ⇒ Object
Initialize the LDAP client and set up the LDAP specific datastore options to allow the client to perform authentication and timeout operations.
-
#ldap_connect(opts = {}, &block) ⇒ Object
The result of whatever the block that was passed in via the “block” parameter yielded.
-
#ldap_new(opts = {}) {|ldap| ... } ⇒ Object
Create a new LDAP connection using Net::LDAP.new and yield the resulting connection object to the caller of this method.
-
#ldap_open(connect_opts, &block) ⇒ Object
Connect to the target LDAP server using the options provided, and pass the resulting connection object to the proc provided.
-
#peer ⇒ String
Return the peer as a host:port formatted string.
- #resolve_connect_opts(connect_opts) ⇒ Object
-
#rhost ⇒ String
Alias to return the RHOST datastore option.
-
#rport ⇒ String
Alias to return the RPORT datastore option.
-
#validate_bind_success!(ldap) ⇒ Nil
Check whether it was possible to successfully bind to the target LDAP server.
-
#validate_query_result!(query_result, filter = nil) ⇒ Nil
Validate the query result and check whether the query succeeded.
Methods included from Metasploit::Framework::LDAP::Client
Methods included from Kerberos::ServiceAuthenticator::Options
Methods included from Kerberos::Ticket::Storage
#kerberos_storage_options, #kerberos_ticket_storage, store_ccache
Instance Method Details
#discover_base_dn(ldap) ⇒ String
Discover the base DN of the target LDAP server via the LDAP server's naming contexts.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 201 def discover_base_dn(ldap) # @type [Net::BER::BerIdentifiedArray] naming_contexts = get_naming_contexts(ldap) unless naming_contexts print_error("#{peer} Base DN cannot be determined") return end # NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN. naming_contexts.select! { |context| context =~ /^([Dd][Cc]=[A-Za-z0-9-]+,?)+$/ } naming_contexts.reject! { |context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/ } if naming_contexts.blank? print_error("#{peer} A base DN matching the expected format could not be found!") return end base_dn = naming_contexts[0] print_good("#{peer} Discovered base DN: #{base_dn}") base_dn end |
#get_connect_opts ⇒ Hash
Set the various connection options to use when connecting to the target LDAP server based on the current datastore options. Returns the resulting connection configuration as a hash.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 76 def get_connect_opts opts = { username: datastore['USERNAME'], password: datastore['PASSWORD'], domain: datastore['DOMAIN'], domain_controller_rhost: datastore['DomainControllerRhost'], ldap_auth: datastore['LDAP::Auth'], ldap_cert_file: datastore['LDAP::CertFile'], ldap_rhostname: datastore['Ldap::Rhostname'], ldap_krb_offered_enc_types: datastore['Ldap::KrbOfferedEncryptionTypes'], ldap_krb5_cname: datastore['Ldap::Krb5Ccname'], proxies: datastore['Proxies'], framework_module: self } ldap_connect_opts(rhost, rport, datastore['LDAP::ConnectTimeout'], ssl: datastore['SSL'], opts: opts) end |
#get_naming_contexts(ldap) ⇒ Net::BER::BerIdentifiedArray
Get the naming contexts for the target LDAP server.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 176 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 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 |
#initialize(info = {}) ⇒ Object
Initialize the LDAP client and set up the LDAP specific datastore options to allow the client to perform authentication and timeout operations. Acts as a wrapper around the caller's implementation of the `initialize` method, which will usually be the module's class's implementation, such as lib/msf/core/auxiliary.rb.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 26 def initialize(info = {}) super ([ Opt::RHOST, Opt::RPORT(389), OptBool.new('SSL', [false, 'Enable SSL on the LDAP connection', false]), Msf::OptString.new('DOMAIN', [false, 'The domain to authenticate to']), Msf::OptString.new('USERNAME', [false, 'The username to authenticate with'], aliases: ['BIND_DN']), Msf::OptString.new('PASSWORD', [false, 'The password to authenticate with'], aliases: ['BIND_PW']) ]) ( [ Opt::Proxies, *(protocol: 'LDAP'), *(protocol: 'LDAP', auth_methods: Msf::Exploit::Remote::AuthOption::LDAP_OPTIONS), Msf::OptPath.new('LDAP::CertFile', [false, 'The path to the PKCS12 (.pfx) certificate file to authenticate with'], conditions: ['LDAP::Auth', '==', Msf::Exploit::Remote::AuthOption::SCHANNEL]), OptFloat.new('LDAP::ConnectTimeout', [true, 'Timeout for LDAP connect', 10.0]) ] ) end |
#ldap_connect(opts = {}, &block) ⇒ Object
Returns The result of whatever the block that was passed in via the “block” parameter yielded.
97 98 99 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 97 def ldap_connect(opts = {}, &block) ldap_open(get_connect_opts.merge(opts), &block) end |
#ldap_new(opts = {}) {|ldap| ... } ⇒ Object
Create a new LDAP connection using Net::LDAP.new and yield the resulting connection object to the caller of this method.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 136 def ldap_new(opts = {}) ldap = Net::LDAP.new(resolve_connect_opts(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 at https://github.com/ruby-ldap/ruby-net-ldap/issues/375 # # @yieldparam conn [Net::LDAP] The LDAP connection handle to use for connecting to # the target LDAP server. # @param args [Hash] A hash containing options for the ldap connection 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 |
#ldap_open(connect_opts, &block) ⇒ Object
Connect to the target LDAP server using the options provided, and pass the resulting connection object to the proc provided. Terminate the connection once the proc finishes executing.
112 113 114 115 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 112 def ldap_open(connect_opts, &block) opts = resolve_connect_opts(connect_opts) Net::LDAP.open(opts, &block) end |
#peer ⇒ String
Return the peer as a host:port formatted string.
66 67 68 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 66 def peer "#{rhost}:#{rport}" end |
#resolve_connect_opts(connect_opts) ⇒ Object
118 119 120 121 122 123 124 125 126 127 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 118 def resolve_connect_opts(connect_opts) return connect_opts unless connect_opts.dig(:auth, :initial_credential).is_a?(Proc) opts = connect_opts.dup # For scenarios such as Kerberos, we might need to make additional calls out to a separate services to acquire an initial credential opts[:auth].merge!( initial_credential: opts[:auth][:initial_credential].call ) opts end |
#rhost ⇒ String
Alias to return the RHOST datastore option.
52 53 54 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 52 def rhost datastore['RHOST'] end |
#rport ⇒ String
Alias to return the RPORT datastore option.
59 60 61 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 59 def rport datastore['RPORT'] end |
#validate_bind_success!(ldap) ⇒ Nil
Check whether it was possible to successfully bind to the target LDAP server. Raise a RuntimeException with an appropriate error message if not.
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 233 def validate_bind_success!(ldap) bind_result = ldap.get_operation_result.table # Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes case bind_result[:code] when 0 vprint_good('Successfully bound to the LDAP server!') when 1 fail_with(Msf::Module::Failure::NoAccess, "An operational error occurred, perhaps due to lack of authorization. The error was: #{bind_result[:error_message].strip}") when 7 fail_with(Msf::Module::Failure::NoTarget, 'Target does not support the simple authentication mechanism!') when 8 fail_with(Msf::Module::Failure::NoTarget, "Server requires a stronger form of authentication than we can provide! The error was: #{bind_result[:error_message].strip}") when 14 fail_with(Msf::Module::Failure::NoTarget, "Server requires additional information to complete the bind. Error was: #{bind_result[:error_message].strip}") when 48 fail_with(Msf::Module::Failure::NoAccess, "Target doesn't support the requested authentication type we sent. Try binding to the same user without a password, or providing credentials if you were doing anonymous authentication.") when 49 fail_with(Msf::Module::Failure::NoAccess, 'Invalid credentials provided!') else fail_with(Msf::Module::Failure::Unknown, "Unknown error occurred whilst binding: #{bind_result[:error_message].strip}") end end |
#validate_query_result!(query_result, filter = nil) ⇒ Nil
Validate the query result and check whether the query succeeded. Fail with an appropriate error code if the query failed.
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 273 def validate_query_result!(query_result, filter=nil) if query_result.class != Hash raise ArgumentError, 'Parameter to "validate_query_result!" function was not a Hash!' end # Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes case query_result[:code] when 0 vprint_status("Successfully queried #{filter}.") if filter.present? when 1 # This is unknown as whilst we could fail on lack of authorization, this is not guaranteed with this error code. # The user will need to inspect the error message to determine the root cause of the issue. fail_with(Msf::Module::Failure::Unknown, "An LDAP operational error occurred. It is likely the client requires authorization! The error was: #{query_result[:error_message].strip}") when 2 fail_with(Msf::Module::Failure::BadConfig, "The LDAP protocol being used by Metasploit isn't supported. The error was #{query_result[:error_message].strip}") when 3 fail_with(Msf::Module::Failure::TimeoutExpired, 'The LDAP server returned a timeout response to the query.') when 4 fail_with(Msf::Module::Failure::UnexpectedReply, 'The LDAP query was determined to result in too many entries for the LDAP server to return.') when 11 fail_with(Msf::Module::Failure::UnexpectedReply, 'The LDAP server indicated some administrative limit within the server whilst the request was being processed.') when 16 fail_with(Msf::Module::Failure::NotFound, 'The LDAP operation failed because the referenced attribute does not exist.') when 18 fail_with(Msf::Module::Failure::BadConfig, 'The LDAP search failed because some matching is not supported for the target attribute type!') when 32 fail_with(Msf::Module::Failure::UnexpectedReply, 'The LDAP search failed because the operation targeted an entity within the base DN that does not exist.') when 33 fail_with(Msf::Module::Failure::BadConfig, "An attempt was made to dereference an alias that didn't resolve properly.") when 34 fail_with(Msf::Module::Failure::BadConfig, 'The request included an invalid base DN entry.') when 50 fail_with(Msf::Module::Failure::NoAccess, 'The LDAP operation failed due to insufficient access rights.') when 51 fail_with(Msf::Module::Failure::UnexpectedReply, 'The LDAP operation failed because the server is too busy to perform the request.') when 52 fail_with(Msf::Module::Failure::UnexpectedReply, 'The LDAP operation failed because the server is not currently available to process the request.') when 53 fail_with(Msf::Module::Failure::UnexpectedReply, 'The LDAP operation failed because the server is unwilling to perform the request.') when 64 fail_with(Msf::Module::Failure::Unknown, 'The LDAP operation failed due to a naming violation.') when 65 fail_with(Msf::Module::Failure::Unknown, 'The LDAP operation failed due to an object class violation.') else if query_result[:error_message].blank? fail_with(Msf::Module::Failure::Unknown, 'The LDAP operation failed but no error message was returned!') else fail_with(Msf::Module::Failure::Unknown, "The LDAP operation failed with error: #{query_result[:error_message].strip}") end end end |