Skip to content

Commit 22cf3f0

Browse files
committed
Send a benign LDAP request every 10 minutes to keep sessions alive
1 parent 00b1d8f commit 22cf3f0

File tree

3 files changed

+62
-1
lines changed

3 files changed

+62
-1
lines changed

lib/msf/base/sessions/ldap.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ class Msf::Sessions::LDAP
1414
# @return [Rex::Proto::LDAP::Client] The LDAP client
1515
attr_accessor :client
1616

17+
attr_accessor :keep_alive_thread
18+
1719
attr_accessor :platform, :arch
1820
attr_reader :framework
1921

@@ -26,6 +28,11 @@ def initialize(rstream, opts = {})
2628
super(rstream, opts)
2729
end
2830

31+
def cleanup
32+
stop_keep_alive_loop
33+
super
34+
end
35+
2936
def bootstrap(datastore = {}, handler = nil)
3037
session = self
3138
session.init_ui(user_input, user_output)
@@ -139,4 +146,32 @@ def _interact_stream
139146
raise EOFError if (console.stopped? == true)
140147
end
141148

149+
def on_registered
150+
start_keep_alive_loop
151+
end
152+
153+
# Start a background thread for regularly sending a no-op command to keep the connection alive
154+
def start_keep_alive_loop
155+
self.keep_alive_thread = framework.threads.spawn('LDAP-shell-keepalive', false) do
156+
keep_alive_timeout = 10 * 60 # 10 minutes
157+
loop do
158+
if client.last_interaction.nil?
159+
remaining_sleep = keep_alive_timeout
160+
else
161+
remaining_sleep = keep_alive_timeout - (Time.now - client.last_interaction)
162+
end
163+
sleep(remaining_sleep)
164+
if (Time.now - client.last_interaction) > keep_alive_timeout
165+
client.search_root_dse
166+
end
167+
# This should have moved last_interaction forwards
168+
fail if (Time.now - client.last_interaction) > keep_alive_timeout
169+
end
170+
end
171+
end
172+
173+
# Stop the background thread
174+
def stop_keep_alive_loop
175+
keep_alive_thread.kill
176+
end
142177
end

lib/msf/core/exploit/remote/ldap.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def resolve_connect_opts(connect_opts)
168168
# the target LDAP server.
169169
def ldap_new(opts = {})
170170
ldap = Rex::Proto::LDAP::Client.new(resolve_connect_opts(get_connect_opts.merge(opts)))
171+
mutex = Mutex.new
171172

172173
# NASTY, but required
173174
# monkey patch ldap object in order to ignore bind errors
@@ -177,13 +178,17 @@ def ldap_new(opts = {})
177178
# "Note that disabling the anonymous bind mechanism does not prevent anonymous
178179
# access to the directory."
179180
# Bug created for Net:LDAP at https://github.com/ruby-ldap/ruby-net-ldap/issues/375
181+
# Also used to support multi-threading (used for keep-alive)
180182
#
181183
# @yieldparam conn [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
182184
# the target LDAP server.
183185
# @param args [Hash] A hash containing options for the ldap connection
184186
def ldap.use_connection(args)
185187
if @open_connection
186-
yield @open_connection
188+
mutex.synchronize do
189+
yield @open_connection
190+
end
191+
register_interaction
187192
else
188193
begin
189194
conn = new_connection

lib/rex/proto/ldap/client.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,23 @@ class Client < Net::LDAP
1111
# @return [Rex::Socket]
1212
attr_reader :socket
1313

14+
# [Time] The last time an interaction occurred on the connection (for keep-alive purposes)
15+
attr_reader :last_interaction
16+
17+
# [Mutex] Control access to the connection. One at a time.
18+
attr_reader :connection_use_mutex
19+
1420
def initialize(args)
1521
@base_dn = args[:base]
22+
@last_interaction = nil
23+
@connection_use_mutex = Mutex.new
1624
super
1725
end
1826

27+
def register_interaction
28+
@last_interaction = Time.now
29+
end
30+
1931
# @return [Array<String>] LDAP servers naming contexts
2032
def naming_contexts
2133
@naming_contexts ||= search_root_dse[:namingcontexts]
@@ -46,6 +58,14 @@ def peerinfo
4658
"#{peerhost}:#{peerport}"
4759
end
4860

61+
def use_connection(args)
62+
@connection_use_mutex.synchronize do
63+
return super(args)
64+
ensure
65+
register_interaction
66+
end
67+
end
68+
4969
# https://github.com/ruby-ldap/ruby-net-ldap/issues/11
5070
# We want to keep the ldap connection open to use later
5171
# but there's no built in way within the `Net::LDAP` library to do that
@@ -65,6 +85,7 @@ def _open
6585
@socket = @open_connection.socket
6686
payload[:connection] = @open_connection
6787
payload[:bind] = @result = @open_connection.bind(@auth)
88+
register_interaction
6889
return self
6990
end
7091
end

0 commit comments

Comments
 (0)