From 522f9ba945d9ee92cff7faeca91cc7f20b01c585 Mon Sep 17 00:00:00 2001 From: Ashley Donaldson Date: Wed, 9 Oct 2024 11:30:25 +1100 Subject: [PATCH] Send a benign LDAP request every 10 minutes to keep sessions alive --- lib/msf/base/sessions/ldap.rb | 35 +++++++++++++++++++++++++++++ lib/msf/core/exploit/remote/ldap.rb | 7 +++++- lib/rex/proto/ldap/client.rb | 21 +++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/lib/msf/base/sessions/ldap.rb b/lib/msf/base/sessions/ldap.rb index 71c9013f781a..fdb17c1344d0 100644 --- a/lib/msf/base/sessions/ldap.rb +++ b/lib/msf/base/sessions/ldap.rb @@ -14,6 +14,8 @@ class Msf::Sessions::LDAP # @return [Rex::Proto::LDAP::Client] The LDAP client attr_accessor :client + attr_accessor :keep_alive_thread + attr_accessor :platform, :arch attr_reader :framework @@ -26,6 +28,11 @@ def initialize(rstream, opts = {}) super(rstream, opts) end + def cleanup + stop_keep_alive_loop + super + end + def bootstrap(datastore = {}, handler = nil) session = self session.init_ui(user_input, user_output) @@ -139,4 +146,32 @@ def _interact_stream raise EOFError if (console.stopped? == true) end + def on_registered + start_keep_alive_loop + end + + # Start a background thread for regularly sending a no-op command to keep the connection alive + def start_keep_alive_loop + self.keep_alive_thread = framework.threads.spawn('LDAP-shell-keepalive', false) do + keep_alive_timeout = 10 * 60 # 10 minutes + loop do + if client.last_interaction.nil? + remaining_sleep = keep_alive_timeout + else + remaining_sleep = keep_alive_timeout - (Time.now - client.last_interaction) + end + sleep(remaining_sleep) + if (Time.now - client.last_interaction) > keep_alive_timeout + client.search_root_dse + end + # This should have moved last_interaction forwards + fail if (Time.now - client.last_interaction) > keep_alive_timeout + end + end + end + + # Stop the background thread + def stop_keep_alive_loop + keep_alive_thread.kill + end end diff --git a/lib/msf/core/exploit/remote/ldap.rb b/lib/msf/core/exploit/remote/ldap.rb index 5346a14d382d..b569ab9dedb9 100644 --- a/lib/msf/core/exploit/remote/ldap.rb +++ b/lib/msf/core/exploit/remote/ldap.rb @@ -168,6 +168,7 @@ def resolve_connect_opts(connect_opts) # the target LDAP server. def ldap_new(opts = {}) ldap = Rex::Proto::LDAP::Client.new(resolve_connect_opts(get_connect_opts.merge(opts))) + mutex = Mutex.new # NASTY, but required # monkey patch ldap object in order to ignore bind errors @@ -177,13 +178,17 @@ def ldap_new(opts = {}) # "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 + mutex.synchronize do + yield @open_connection + end + register_interaction else begin conn = new_connection diff --git a/lib/rex/proto/ldap/client.rb b/lib/rex/proto/ldap/client.rb index 608edaa84770..8024a1fedb53 100644 --- a/lib/rex/proto/ldap/client.rb +++ b/lib/rex/proto/ldap/client.rb @@ -11,11 +11,23 @@ class Client < Net::LDAP # @return [Rex::Socket] attr_reader :socket + # [Time] The last time an interaction occurred on the connection (for keep-alive purposes) + attr_reader :last_interaction + + # [Mutex] Control access to the connection. One at a time. + attr_reader :connection_use_mutex + def initialize(args) @base_dn = args[:base] + @last_interaction = nil + @connection_use_mutex = Mutex.new super end + def register_interaction + @last_interaction = Time.now + end + # @return [Array] LDAP servers naming contexts def naming_contexts @naming_contexts ||= search_root_dse[:namingcontexts] @@ -46,6 +58,14 @@ def peerinfo "#{peerhost}:#{peerport}" end + def use_connection(args) + @connection_use_mutex.synchronize do + return super(args) + ensure + register_interaction + end + end + # https://github.com/ruby-ldap/ruby-net-ldap/issues/11 # We want to keep the ldap connection open to use later # but there's no built in way within the `Net::LDAP` library to do that @@ -65,6 +85,7 @@ def _open @socket = @open_connection.socket payload[:connection] = @open_connection payload[:bind] = @result = @open_connection.bind(@auth) + register_interaction return self end end