2020-04-15 04:51:39 -05:00
# -*- coding: binary -*-
#
2024-04-03 14:42:35 +01:00
# This mixin is a wrapper around Rex::Proto::LDAP::Client
2020-04-15 04:51:39 -05:00
#
2021-12-16 18:47:52 -05:00
require 'rex/proto/ldap'
2023-07-19 13:05:23 +01:00
require 'metasploit/framework/ldap/client'
2020-04-15 04:51:39 -05:00
module Msf
2020-07-25 00:13:12 +02:00
module Exploit::Remote::LDAP
2022-12-14 12:59:27 -05:00
include Msf :: Exploit :: Remote :: Kerberos :: Ticket :: Storage
2023-01-20 11:31:24 +00:00
include Msf :: Exploit :: Remote :: Kerberos :: ServiceAuthenticator :: Options
2023-07-05 12:48:57 +01:00
include Metasploit :: Framework :: LDAP :: Client
2022-12-14 12:59:27 -05:00
2022-12-30 16:07:08 -06:00
# Initialize the LDAP client and set up the LDAP specific datastore
2023-01-04 11:09:23 -06:00
# 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.
2022-12-30 16:07:08 -06:00
#
# @param info [Hash] A hash containing information about the module
# using this library which includes its name, description, author, references,
# disclosure date, license, actions, default action, default options,
# and notes.
2020-07-25 00:13:12 +02:00
def initialize ( info = { } )
super
register_options ( [
2024-05-02 13:57:13 +01:00
Opt :: RHOST ,
Opt :: RPORT ( 389 ) ,
2020-07-25 00:13:12 +02:00
OptBool . new ( 'SSL' , [ false , 'Enable SSL on the LDAP connection' , false ] ) ,
2024-10-31 10:52:49 +00:00
Msf :: OptString . new ( 'LDAPDomain' , [ false , 'The domain to authenticate to' ] , fallbacks : [ 'DOMAIN' ] ) ,
Msf :: OptString . new ( 'LDAPUsername' , [ false , 'The username to authenticate with' ] , fallbacks : %w[ USERNAME BIND_DN ] ) ,
Msf :: OptString . new ( 'LDAPPassword' , [ false , 'The password to authenticate with' ] , fallbacks : %w[ PASSWORD BIND_PW ] )
2020-07-25 00:13:12 +02:00
] )
2023-01-20 11:31:24 +00:00
register_advanced_options (
[
2023-06-14 00:40:33 +01:00
Opt :: Proxies ,
2023-01-20 11:31:24 +00:00
* kerberos_storage_options ( protocol : 'LDAP' ) ,
* kerberos_auth_options ( protocol : 'LDAP' , auth_methods : Msf :: Exploit :: Remote :: AuthOption :: LDAP_OPTIONS ) ,
2023-02-27 11:07:21 -06:00
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 ] ) ,
2024-05-02 16:25:10 -04:00
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 ] ] )
2023-01-20 11:31:24 +00:00
]
)
2020-07-25 00:13:12 +02:00
end
2020-04-15 04:51:39 -05:00
2022-12-30 16:07:08 -06:00
# Alias to return the RHOST datastore option.
#
2023-01-04 11:09:23 -06:00
# @return [String] The current value of RHOST in the datastore.
2020-07-25 00:13:12 +02:00
def rhost
datastore [ 'RHOST' ]
end
2020-07-22 14:23:00 -05:00
2022-12-30 16:07:08 -06:00
# Alias to return the RPORT datastore option.
#
2023-01-04 11:09:23 -06:00
# @return [String] The current value of RPORT in the datastore.
2020-07-25 00:13:12 +02:00
def rport
datastore [ 'RPORT' ]
end
2020-04-15 04:51:39 -05:00
2022-12-30 16:07:08 -06:00
# Return the peer as a host:port formatted string.
#
# @return [String] A string containing the peer details in RHOST:RPORT format.
2020-07-25 00:13:12 +02:00
def peer
" #{ rhost } : #{ rport } "
end
2020-04-15 04:51:39 -05:00
2022-12-30 16:07:08 -06:00
# Set the various connection options to use when connecting to the
2023-01-04 11:09:23 -06:00
# target LDAP server based on the current datastore options. Returns
# the resulting connection configuration as a hash.
2022-12-30 16:07:08 -06:00
#
# @return [Hash] The options to use when connecting to the target
# LDAP server.
2021-12-18 09:03:56 -05:00
def get_connect_opts
2023-07-05 12:48:57 +01:00
opts = {
2024-10-31 10:52:49 +00:00
username : datastore [ 'LDAPUsername' ] ,
password : datastore [ 'LDAPPassword' ] ,
domain : datastore [ 'LDAPDomain' ] ,
2024-04-22 23:03:56 +01:00
base : datastore [ 'BASE_DN' ] ,
2023-07-05 12:48:57 +01:00
domain_controller_rhost : datastore [ 'DomainControllerRhost' ] ,
ldap_auth : datastore [ 'LDAP::Auth' ] ,
ldap_cert_file : datastore [ 'LDAP::CertFile' ] ,
2024-05-02 16:25:10 -04:00
ldap_rhostname : datastore [ 'LDAP::Rhostname' ] ,
ldap_krb_offered_enc_types : datastore [ 'LDAP::KrbOfferedEncryptionTypes' ] ,
ldap_krb5_cname : datastore [ 'LDAP::Krb5Ccname' ] ,
2023-07-18 22:48:00 +01:00
proxies : datastore [ 'Proxies' ] ,
2024-04-23 10:19:51 -04:00
framework_module : self ,
kerberos_ticket_storage : kerberos_ticket_storage
2020-07-25 00:13:12 +02:00
}
2024-05-02 16:25:10 -04:00
case datastore [ 'LDAP::Signing' ]
when 'required'
opts [ :sign_and_seal ] = true
when 'disabled'
opts [ :sign_and_seal ] = false
end
2020-04-15 04:51:39 -05:00
2024-05-02 16:25:10 -04:00
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 . message )
end
2024-04-24 15:05:03 +10:00
# Now that the options have been resolved (including auto possibly resolving to NTLM), check whether this is a valid config
2024-05-02 16:25:10 -04:00
if result [ :auth ] && datastore [ 'LDAP::Signing' ] == 'required'
2024-04-23 15:55:09 -04:00
unless % i [ rex_kerberos rex_ntlm ] . include? ( result [ :auth ] [ :method ] ) || ( result [ :auth ] [ :method ] == :sasl && result [ :auth ] [ :mechanism ] == 'GSS-SPNEGO' )
2024-05-02 16:25:10 -04:00
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
2024-04-24 15:05:03 +10:00
end
result
2023-07-18 22:48:00 +01:00
end
2023-07-05 12:48:57 +01:00
2023-07-18 22:48:00 +01:00
# @see #ldap_open
# @return [Object] The result of whatever the block that was
# passed in via the "block" parameter yielded.
def ldap_connect ( opts = { } , & block )
ldap_open ( get_connect_opts . merge ( opts ) , & block )
2020-07-22 14:23:00 -05:00
end
2022-12-30 16:07:08 -06:00
# Connect to the target LDAP server using the options provided,
2023-01-04 11:09:23 -06:00
# and pass the resulting connection object to the proc provided.
2024-05-15 16:11:40 +01:00
# Terminate the connection once the proc finishes executing unless
# `keep_open` is set to true
2022-12-30 16:07:08 -06:00
#
2023-07-24 19:13:04 +01:00
# @param connect_opts [Hash] Options for the LDAP connection.
2024-05-15 16:11:40 +01:00
# @param keep_open [Boolean] Keep the connection open or close once the block is finished
2023-01-05 17:44:24 -06:00
# @param block [Proc] A proc containing the functionality to execute
2022-12-30 16:07:08 -06:00
# after the LDAP connection has succeeded. The connection is closed
# once this proc finishes executing.
2024-04-03 14:42:35 +01:00
# @see Rex::Proto::LDAP::Client.open
2023-01-05 10:51:18 -06:00
# @return [Object] The result of whatever the block that was
# passed in via the "block" parameter yielded.
2024-05-15 16:11:40 +01:00
def ldap_open ( connect_opts , keep_open : false , & block )
2023-07-18 22:48:00 +01:00
opts = resolve_connect_opts ( connect_opts )
2024-05-15 16:11:40 +01:00
if keep_open
Rex :: Proto :: LDAP :: Client . _open ( opts , & block )
else
Rex :: Proto :: LDAP :: Client . open ( opts , & block )
end
2023-07-18 22:48:00 +01:00
end
def resolve_connect_opts ( connect_opts )
2023-07-24 19:13:04 +01:00
return connect_opts unless connect_opts . dig ( :auth , :initial_credential ) . is_a? ( Proc )
2023-07-18 22:48:00 +01:00
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
2020-07-25 00:13:12 +02:00
end
2020-04-15 10:32:17 -05:00
2024-04-03 14:42:35 +01:00
# Create a new LDAP connection using Rex::Proto::LDAP::Client.new and yield the
2023-01-04 11:09:23 -06:00
# resulting connection object to the caller of this method.
2022-12-30 16:07:08 -06:00
#
# @param opts [Hash] A hash containing the connection options for the
# LDAP connection to the target server.
2024-04-03 14:42:35 +01:00
# @yieldparam ldap [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
2023-01-04 11:09:23 -06:00
# the target LDAP server.
2020-07-25 00:13:12 +02:00
def ldap_new ( opts = { } )
2024-04-03 14:42:35 +01:00
ldap = Rex :: Proto :: LDAP :: Client . new ( resolve_connect_opts ( get_connect_opts . merge ( opts ) ) )
2020-07-25 00:13:12 +02:00
# NASTY, but required
2023-01-04 11:09:23 -06:00
# 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
2024-10-09 11:30:25 +11:00
# Also used to support multi-threading (used for keep-alive)
2020-07-25 00:13:12 +02:00
#
2024-04-03 14:42:35 +01:00
# @yieldparam conn [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
2023-01-04 11:09:23 -06:00
# the target LDAP server.
2022-12-30 16:07:08 -06:00
# @param args [Hash] A hash containing options for the ldap connection
2020-07-25 00:13:12 +02:00
def ldap . use_connection ( args )
if @open_connection
2024-10-10 17:17:02 +11:00
yield @open_connection
2024-10-09 11:30:25 +11:00
register_interaction
2020-07-25 00:13:12 +02:00
else
begin
conn = new_connection
conn . bind ( args [ :auth ] || @auth )
# Commented out vs. original
# result = conn.bind(args[:auth] || @auth)
2024-04-03 14:42:35 +01:00
# return result unless result.result_code == Rex::Proto::LDAP::Client::ResultCodeSuccess
2020-07-25 00:13:12 +02:00
yield conn
ensure
conn . close if conn
end
end
end
yield ldap
2020-04-15 10:32:17 -05:00
end
2022-12-30 16:07:08 -06:00
# Check whether it was possible to successfully bind to the target LDAP
2023-01-04 11:09:23 -06:00
# server. Raise a RuntimeException with an appropriate error message
# if not.
2022-12-30 16:07:08 -06:00
#
2024-04-03 14:42:35 +01:00
# @param ldap [Rex::Proto::LDAP::Client] The Rex::Proto::LDAP::Client connection handle for the
2022-12-30 16:07:08 -06:00
# current LDAP connection.
#
# @raise [RuntimeError] A RuntimeError will be raised if the LDAP
# bind request failed.
2023-01-04 11:09:23 -06:00
# @return [Nil] This function does not return any data.
2022-10-24 13:58:27 -04:00
def validate_bind_success! ( ldap )
2025-06-09 18:36:47 -04:00
if respond_to? ( :session ) && session
2024-05-02 13:57:13 +01:00
vprint_good ( 'Successfully bound to the LDAP server via existing SESSION!' )
return
end
2023-02-24 13:50:04 -06:00
bind_result = ldap . get_operation_result . table
2022-10-24 13:58:27 -04:00
# Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
2023-02-24 13:50:04 -06:00
case bind_result [ :code ]
2022-10-24 13:58:27 -04:00
when 0
2022-10-08 01:28:35 -05:00
vprint_good ( 'Successfully bound to the LDAP server!' )
2022-10-24 13:58:27 -04:00
when 1
2023-02-24 13:50:04 -06:00
fail_with ( Msf :: Module :: Failure :: NoAccess , " An operational error occurred, perhaps due to lack of authorization. The error was: #{ bind_result [ :error_message ] . strip } " )
2022-10-24 13:58:27 -04:00
when 7
2023-01-17 14:27:16 -05:00
fail_with ( Msf :: Module :: Failure :: NoTarget , 'Target does not support the simple authentication mechanism!' )
2022-10-24 13:58:27 -04:00
when 8
2024-04-24 13:37:27 +10:00
signing_statement = ''
2024-05-06 09:40:28 -04:00
signing_statement = 'May require LDAP signing to be enabled (`set LDAP::Signing auto`). ' unless %w[ auto required ] . include? ( datastore [ 'LDAP::Signing' ] )
2024-04-24 13:37:27 +10:00
fail_with ( Msf :: Module :: Failure :: NoTarget , " Server requires a stronger form of authentication! #{ signing_statement } The error was: #{ bind_result [ :error_message ] . strip } " )
2022-10-24 13:58:27 -04:00
when 14
2023-02-24 13:50:04 -06:00
fail_with ( Msf :: Module :: Failure :: NoTarget , " Server requires additional information to complete the bind. Error was: #{ bind_result [ :error_message ] . strip } " )
2022-10-24 13:58:27 -04:00
when 48
2023-01-17 14:27:16 -05:00
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. " )
2022-10-24 13:58:27 -04:00
when 49
2023-01-17 14:27:16 -05:00
fail_with ( Msf :: Module :: Failure :: NoAccess , 'Invalid credentials provided!' )
2022-10-24 13:58:27 -04:00
else
2023-02-24 13:50:04 -06:00
fail_with ( Msf :: Module :: Failure :: Unknown , " Unknown error occurred whilst binding: #{ bind_result [ :error_message ] . strip } " )
2022-10-24 13:58:27 -04:00
end
end
2022-10-08 01:28:35 -05:00
2022-12-30 16:07:08 -06:00
# Validate the query result and check whether the query succeeded.
2023-01-04 11:09:23 -06:00
# Fail with an appropriate error code if the query failed.
2022-12-30 16:07:08 -06:00
#
# @param query_result [Hash] A hash containing the results of the query
2023-02-24 13:50:04 -06:00
# as a 'extended_response' representing the extended response,
# a 'code' with an integer representing the result code,
# a 'error_message' containing an optional error message as a Net::BER::BerIdentifiedString,
# a 'matched_dn' containing the matched DN,
# and a 'message' containing the query result message.
2024-04-03 14:42:35 +01:00
# @param filter [Rex::Proto::LDAP::Client::Filter] A Rex::Proto::LDAP::Client::Filter to use to
2022-12-30 16:07:08 -06:00
# filter the results of the query.
#
2023-01-04 11:09:23 -06:00
# @raise [RuntimeError, ArgumentError] A RuntimeError will be raised if the LDAP
# request failed. Alternatively, if the query_result parameter isn't a hash, then an
# ArgumentError will be raised.
# @return [Nil] This function does not return any data.
2023-04-28 10:09:37 -04:00
def validate_query_result! ( query_result , filter = nil )
2022-10-08 01:28:35 -05:00
if query_result . class != Hash
2023-02-24 13:50:04 -06:00
raise ArgumentError , 'Parameter to "validate_query_result!" function was not a Hash!'
2022-10-08 01:28:35 -05:00
end
2022-10-28 14:16:49 -05:00
# Codes taken from https://ldap.com/ldap-result-code-reference-core-ldapv3-result-codes
2023-02-24 13:50:04 -06:00
case query_result [ :code ]
2022-10-08 01:28:35 -05:00
when 0
2023-04-28 10:09:37 -04:00
vprint_status ( " Successfully queried #{ filter } . " ) if filter . present?
2022-10-08 01:28:35 -05:00
when 1
2022-10-28 14:16:49 -05:00
# 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.
2023-04-28 10:09:37 -04:00
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 } " )
2022-10-28 14:16:49 -05:00
when 2
2023-02-24 13:50:04 -06:00
fail_with ( Msf :: Module :: Failure :: BadConfig , " The LDAP protocol being used by Metasploit isn't supported. The error was #{ query_result [ :error_message ] . strip } " )
2022-10-28 14:16:49 -05:00
when 3
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: TimeoutExpired , 'The LDAP server returned a timeout response to the query.' )
2022-10-28 14:16:49 -05:00
when 4
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: UnexpectedReply , 'The LDAP query was determined to result in too many entries for the LDAP server to return.' )
2022-10-28 14:16:49 -05:00
when 11
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: UnexpectedReply , 'The LDAP server indicated some administrative limit within the server whilst the request was being processed.' )
2022-10-28 14:16:49 -05:00
when 16
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: NotFound , 'The LDAP operation failed because the referenced attribute does not exist.' )
2022-10-28 14:16:49 -05:00
when 18
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: BadConfig , 'The LDAP search failed because some matching is not supported for the target attribute type!' )
2024-11-21 17:11:17 +11:00
when 19
fail_with ( Msf :: Module :: Failure :: BadConfig , 'A constraint on the operation was not satisfied' )
2022-10-28 14:16:49 -05:00
when 32
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: UnexpectedReply , 'The LDAP search failed because the operation targeted an entity within the base DN that does not exist.' )
2022-10-28 14:16:49 -05:00
when 33
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: BadConfig , " An attempt was made to dereference an alias that didn't resolve properly. " )
2022-10-28 14:16:49 -05:00
when 34
2023-01-17 14:27:16 -05:00
fail_with ( Msf :: Module :: Failure :: BadConfig , 'The request included an invalid base DN entry.' )
2022-10-28 14:16:49 -05:00
when 50
2023-01-17 14:27:16 -05:00
fail_with ( Msf :: Module :: Failure :: NoAccess , 'The LDAP operation failed due to insufficient access rights.' )
2022-10-28 14:16:49 -05:00
when 51
2023-01-17 14:27:16 -05:00
fail_with ( Msf :: Module :: Failure :: UnexpectedReply , 'The LDAP operation failed because the server is too busy to perform the request.' )
2022-10-28 14:16:49 -05:00
when 52
2023-01-17 14:27:16 -05:00
fail_with ( Msf :: Module :: Failure :: UnexpectedReply , 'The LDAP operation failed because the server is not currently available to process the request.' )
2022-10-28 14:16:49 -05:00
when 53
2023-01-17 14:27:16 -05:00
fail_with ( Msf :: Module :: Failure :: UnexpectedReply , 'The LDAP operation failed because the server is unwilling to perform the request.' )
2022-10-28 14:16:49 -05:00
when 64
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: Unknown , 'The LDAP operation failed due to a naming violation.' )
2022-10-28 14:16:49 -05:00
when 65
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: Unknown , 'The LDAP operation failed due to an object class violation.' )
2022-10-08 01:28:35 -05:00
else
2023-02-24 13:50:04 -06:00
if query_result [ :error_message ] . blank?
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: Unknown , 'The LDAP operation failed but no error message was returned!' )
2022-10-08 01:28:35 -05:00
else
2023-04-28 10:09:37 -04:00
fail_with ( Msf :: Module :: Failure :: Unknown , " The LDAP operation failed with error: #{ query_result [ :error_message ] . strip } " )
2022-10-08 01:28:35 -05:00
end
end
end
2024-06-18 17:39:06 -04:00
# Return a string suitable for placement in an LDAP filter
# e.g. (certificateTemplates=#{ldap_escape_string(name)})
#
# @param string String The string to escape.
# @return The escaped string.
def ldap_escape_filter ( string )
Net :: LDAP :: Filter . escape ( string )
end
2020-07-25 00:13:12 +02:00
end
2020-04-15 04:51:39 -05:00
end