Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new Ldap session type #19058

Merged
9 commits merged into from
May 15, 2024
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
Loading