Skip to content

Commit

Permalink
Land #19058, Add new Ldap session type
Browse files Browse the repository at this point in the history
Merge branch 'land-19058' into upstream-master
  • Loading branch information
bwatters-r7 committed May 15, 2024
2 parents f6e7aac + 68f7334 commit f3a8b35
Show file tree
Hide file tree
Showing 40 changed files with 1,327 additions and 305 deletions.
24 changes: 16 additions & 8 deletions lib/metasploit/framework/login_scanner/ldap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ class LDAP
include Metasploit::Framework::LDAP::Client
include Msf::Exploit::Remote::LDAP

attr_accessor :opts
attr_accessor :realm_key
attr_accessor :opts, :realm_key
# @!attribute use_client_as_proof
# @return [Boolean] If a login is successful and this attribute is true - an LDAP::Client instance is used as proof
attr_accessor :use_client_as_proof

def attempt_login(credential)
result_opts = {
Expand All @@ -36,17 +38,24 @@ def do_login(credential)
}.merge(@opts)

connect_opts = ldap_connect_opts(host, port, connection_timeout, ssl: opts[:ssl], opts: opts)
ldap_open(connect_opts) do |ldap|
return status_code(ldap.get_operation_result.table)
begin
ldap_client = ldap_open(connect_opts, keep_open: true)
return status_code(ldap_client)
rescue StandardError => e
{ status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e }
end
end

def status_code(operation_result)
case operation_result[:code]
def status_code(ldap_client)
operation_result = ldap_client.get_operation_result.table[:code]
case operation_result
when 0
{ status: Metasploit::Model::Login::Status::SUCCESSFUL }
result = { status: Metasploit::Model::Login::Status::SUCCESSFUL }
if use_client_as_proof
result[:proof] = ldap_client
result[:connection] = ldap_client.socket
end
result
else
{ status: Metasploit::Model::Login::Status::INCORRECT, proof: "Bind Result: #{operation_result}" }
end
Expand Down Expand Up @@ -84,7 +93,6 @@ def each_credential
credential.public = "#{credential.public}@#{opts[:domain]}"
yield credential
end

end
end
end
Expand Down
11 changes: 11 additions & 0 deletions lib/msf/base/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ def self.smb_session_history
self.new.smb_session_history
end

# Returns the full path to the ldap session history file.
#
# @return [String] path to the history file.
def self.ldap_session_history
self.new.ldap_session_history
end

# Returns the full path to the PostgreSQL session history file.
#
# @return [String] path to the history file.
Expand Down Expand Up @@ -351,6 +358,10 @@ def smb_session_history
config_directory + FileSep + "smb_session_history"
end

def ldap_session_history
config_directory + FileSep + "ldap_session_history"
end

def postgresql_session_history
config_directory + FileSep + "postgresql_session_history"
end
Expand Down
142 changes: 142 additions & 0 deletions lib/msf/base/sessions/ldap.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# -*- coding: binary -*-

require 'rex/post/ldap'

class Msf::Sessions::LDAP
#
# This interface supports basic interaction.
#
include Msf::Session::Basic
include Msf::Sessions::Scriptable

# @return [Rex::Post::LDAP::Ui::Console] The interactive console
attr_accessor :console
# @return [Rex::Proto::LDAP::Client] The LDAP client
attr_accessor :client

attr_accessor :platform, :arch
attr_reader :framework

# @param[Rex::IO::Stream] rstream
# @param [Hash] opts
# @option opts [Rex::Proto::LDAP::Client] :client
def initialize(rstream, opts = {})
@client = opts.fetch(:client)
self.console = Rex::Post::LDAP::Ui::Console.new(self)
super(rstream, opts)
end

def bootstrap(datastore = {}, handler = nil)
session = self
session.init_ui(user_input, user_output)

@info = "LDAP #{datastore['USERNAME']} @ #{@peer_info}"
end

def execute_file(full_path, args)
if File.extname(full_path) == '.rb'
Rex::Script::Shell.new(self, full_path).run(args)
else
console.load_resource(full_path)
end
end

def process_autoruns(datastore)
['InitialAutoRunScript', 'AutoRunScript'].each do |key|
next if datastore[key].nil? || datastore[key].empty?

args = Shellwords.shellwords(datastore[key])
print_status("Session ID #{sid} (#{tunnel_to_s}) processing #{key} '#{datastore[key]}'")
execute_script(args.shift, *args)
end
end

def type
self.class.type
end

# Returns the type of session.
#
def self.type
'ldap'
end

def self.can_cleanup_files
false
end

#
# Returns the session description.
#
def desc
'LDAP'
end

def address
@address ||= client.peerhost
end

def port
@port ||= client.peerport
end

##
# :category: Msf::Session::Interactive implementors
#
# Initializes the console's I/O handles.
#
def init_ui(input, output)
self.user_input = input
self.user_output = output
console.init_ui(input, output)
console.set_log_source(log_source)

super
end

##
# :category: Msf::Session::Interactive implementors
#
# Resets the console's I/O handles.
#
def reset_ui
console.unset_log_source
console.reset_ui
end

def exit
console.stop
end

##
# :category: Msf::Session::Interactive implementors
#
# Override the basic session interaction to use shell_read and
# shell_write instead of operating on rstream directly.
def _interact
framework.events.on_session_interact(self)
framework.history_manager.with_context(name: type.to_sym) do
_interact_stream
end
end

##
# :category: Msf::Session::Interactive implementors
#
def _interact_stream
framework.events.on_session_interact(self)

console.framework = framework
# Call the console interaction of the ldap client and
# pass it a block that returns whether or not we should still be
# interacting. This will allow the shell to abort if interaction is
# canceled.
console.interact { interacting != true }
console.framework = nil

# If the stop flag has been set, then that means the user exited. Raise
# the EOFError so we can drop this handle like a bad habit.
raise EOFError if (console.stopped? == true)
end

end
16 changes: 8 additions & 8 deletions lib/msf/core/exploit/remote/kerberos/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module Client

# @!attribute client
# @return [Rex::Proto::Kerberos::Client] The kerberos client
attr_accessor :client
attr_accessor :kerberos_client

def initialize(info = {})
super
Expand Down Expand Up @@ -96,20 +96,20 @@ def connect(opts={})
protocol: 'tcp'
)

disconnect if client
self.client = kerb_client
disconnect if kerberos_client
self.kerberos_client = kerb_client

kerb_client
end

# Disconnects the Kerberos client
#
# @param kerb_client [Rex::Proto::Kerberos::Client] the client to disconnect
def disconnect(kerb_client = client)
def disconnect(kerb_client = kerberos_client)
kerb_client.close if kerb_client

if kerb_client == client
self.client = nil
if kerb_client == kerberos_client
self.kerberos_client = nil
end
end

Expand All @@ -129,7 +129,7 @@ def cleanup
def send_request_as(opts = {})
connect(opts)
req = opts.fetch(:req) { build_as_request(opts) }
res = client.send_recv(req)
res = kerberos_client.send_recv(req)
disconnect
res
end
Expand All @@ -143,7 +143,7 @@ def send_request_as(opts = {})
def send_request_tgs(opts = {})
connect(opts)
req = opts.fetch(:req) { build_tgs_request(opts) }
res = client.send_recv(req)
res = kerberos_client.send_recv(req)
disconnect
res
end
Expand Down
Loading

0 comments on commit f3a8b35

Please sign in to comment.