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,
lib/msf/core/exploit/remote/ldap/error.rb,
lib/msf/core/exploit/remote/ldap/queries.rb
Overview
This module exposes methods for querying a remote LDAP service
Defined Under Namespace
Modules: Queries, Server Classes: Error
Instance Method Summary collapse
-
#get_connect_opts ⇒ Hash
Set the various connection options to use when connecting to the target LDAP server based on the current datastore options.
-
#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_escape_filter(string) ⇒ Object
Return a string suitable for placement in an LDAP filter e.g.
-
#ldap_new(opts = {}) {|ldap| ... } ⇒ Object
Create a new LDAP connection using Rex::Proto::LDAP::Client.new and yield the resulting connection object to the caller of this method.
-
#ldap_open(connect_opts, keep_open: false, &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
#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.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 77 def get_connect_opts opts = { username: datastore['USERNAME'], password: datastore['PASSWORD'], domain: datastore['DOMAIN'], base: datastore['BASE_DN'], 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, kerberos_ticket_storage: kerberos_ticket_storage } case datastore['LDAP::Signing'] when 'required' opts[:sign_and_seal] = true when 'disabled' opts[:sign_and_seal] = false end begin result = ldap_connect_opts(rhost, rport, datastore['LDAP::ConnectTimeout'], ssl: datastore['SSL'], opts: opts) rescue Msf::ValidationError => e fail_with(Msf::Module::Failure::BadConfig, e.) end # Now that the options have been resolved (including auto possibly resolving to NTLM), check whether this is a valid config if result[:auth] && datastore['LDAP::Signing'] == 'required' unless %i[ rex_kerberos rex_ntlm ].include?(result[:auth][:method]) || (result[:auth][:method] == :sasl && result[:auth][:mechanism] == 'GSS-SPNEGO') fail_with(Msf::Module::Failure::BadConfig, 'The authentication configuration does not support signing. Change either LDAP::Auth or LDAP::Signing.') end if result[:encryption] # Domain Controllers don't seem to support signing and connection over SSL. Gotta pick one or the other. fail_with(Msf::Module::Failure::BadConfig, 'SSL not supported with signing. Change either SSL or LDAP::Signing.') end end result 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 48 |
# 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]), OptEnum.new('LDAP::Signing', [true, 'Use signed and sealed (encrypted) LDAP', 'auto', %w[ disabled auto required ]]) ] ) end |
#ldap_connect(opts = {}, &block) ⇒ Object
Returns The result of whatever the block that was passed in via the “block” parameter yielded.
124 125 126 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 124 def ldap_connect(opts = {}, &block) ldap_open(get_connect_opts.merge(opts), &block) end |
#ldap_escape_filter(string) ⇒ Object
Return a string suitable for placement in an LDAP filter e.g. (certificateTemplates=#ldap_escape_string(name))
320 321 322 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 320 def ldap_escape_filter(string) Net::LDAP::Filter.escape(string) end |
#ldap_new(opts = {}) {|ldap| ... } ⇒ Object
Create a new LDAP connection using Rex::Proto::LDAP::Client.new and yield the resulting connection object to the caller of this method.
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 169 def ldap_new(opts = {}) ldap = Rex::Proto::LDAP::Client.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 # Also used to support multi-threading (used for keep-alive) # # @yieldparam conn [Rex::Proto::LDAP::Client] 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 register_interaction 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 == Rex::Proto::LDAP::Client::ResultCodeSuccess yield conn ensure conn.close if conn end end end yield ldap end |
#ldap_open(connect_opts, keep_open: false, &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 unless ‘keep_open` is set to true
141 142 143 144 145 146 147 148 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 141 def ldap_open(connect_opts, keep_open: false, &block) opts = resolve_connect_opts(connect_opts) if keep_open Rex::Proto::LDAP::Client._open(opts, &block) else Rex::Proto::LDAP::Client.open(opts, &block) end end |
#peer ⇒ String
Return the peer as a host:port formatted string.
67 68 69 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 67 def peer "#{rhost}:#{rport}" end |
#resolve_connect_opts(connect_opts) ⇒ Object
151 152 153 154 155 156 157 158 159 160 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 151 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.
53 54 55 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 53 def rhost datastore['RHOST'] end |
#rport ⇒ String
Alias to return the RPORT datastore option.
60 61 62 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 60 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.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 215 def validate_bind_success!(ldap) if defined?(:session) && session vprint_good('Successfully bound to the LDAP server via existing SESSION!') return end 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 signing_statement = '' signing_statement = 'May require LDAP signing to be enabled (`set LDAP::Signing auto`). ' unless %w[ auto required ].include?(datastore['LDAP::Signing']) fail_with(Msf::Module::Failure::NoTarget, "Server requires a stronger form of authentication! #{signing_statement}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.
263 264 265 266 267 268 269 270 271 272 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 |
# File 'lib/msf/core/exploit/remote/ldap.rb', line 263 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 |