Skip to content

Commit

Permalink
Send a benign LDAP request every 10 minutes to keep sessions alive
Browse files Browse the repository at this point in the history
  • Loading branch information
smashery committed Oct 9, 2024
1 parent 8e2dbbb commit 522f9ba
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 1 deletion.
35 changes: 35 additions & 0 deletions lib/msf/base/sessions/ldap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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
7 changes: 6 additions & 1 deletion lib/msf/core/exploit/remote/ldap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions lib/rex/proto/ldap/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>] LDAP servers naming contexts
def naming_contexts
@naming_contexts ||= search_root_dse[:namingcontexts]
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 522f9ba

Please sign in to comment.