Skip to content

Commit 9cb3fef

Browse files
authored
Land rapid7#19539, Keep LDAP sessions alive
2 parents d2b4175 + fa2b7e5 commit 9cb3fef

File tree

5 files changed

+67
-3
lines changed

5 files changed

+67
-3
lines changed

lib/msf/base/sessions/ldap.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,30 @@ 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+
19+
# @return [Integer] Seconds between keepalive requests
20+
attr_accessor :keepalive_seconds
21+
1722
attr_accessor :platform, :arch
1823
attr_reader :framework
1924

2025
# @param[Rex::IO::Stream] rstream
2126
# @param [Hash] opts
2227
# @option opts [Rex::Proto::LDAP::Client] :client
28+
# @option opts [Integer] :keepalive
2329
def initialize(rstream, opts = {})
2430
@client = opts.fetch(:client)
31+
@keepalive_seconds = opts.fetch(:keepalive_seconds)
2532
self.console = Rex::Post::LDAP::Ui::Console.new(self)
2633
super(rstream, opts)
2734
end
2835

36+
def cleanup
37+
stop_keep_alive_loop
38+
super
39+
end
40+
2941
def bootstrap(datastore = {}, handler = nil)
3042
session = self
3143
session.init_ui(user_input, user_output)
@@ -139,4 +151,31 @@ def _interact_stream
139151
raise EOFError if (console.stopped? == true)
140152
end
141153

154+
def on_registered
155+
start_keep_alive_loop
156+
end
157+
158+
# Start a background thread for regularly sending a no-op command to keep the connection alive
159+
def start_keep_alive_loop
160+
self.keep_alive_thread = framework.threads.spawn("LDAP-shell-keepalive-#{sid}", false) do
161+
loop do
162+
if client.last_interaction.nil?
163+
remaining_sleep = @keepalive_seconds
164+
else
165+
remaining_sleep = @keepalive_seconds - (Process.clock_gettime(Process::CLOCK_MONOTONIC) - client.last_interaction)
166+
end
167+
sleep(remaining_sleep)
168+
if (Process.clock_gettime(Process::CLOCK_MONOTONIC) - client.last_interaction) > @keepalive_seconds
169+
client.search_root_dse
170+
end
171+
# This should have moved last_interaction forwards
172+
fail if (Process.clock_gettime(Process::CLOCK_MONOTONIC) - client.last_interaction) > @keepalive_seconds
173+
end
174+
end
175+
end
176+
177+
# Stop the background thread
178+
def stop_keep_alive_loop
179+
keep_alive_thread.kill
180+
end
142181
end

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,13 +177,15 @@ def ldap_new(opts = {})
177177
# "Note that disabling the anonymous bind mechanism does not prevent anonymous
178178
# access to the directory."
179179
# Bug created for Net:LDAP at https://github.com/ruby-ldap/ruby-net-ldap/issues/375
180+
# Also used to support multi-threading (used for keep-alive)
180181
#
181182
# @yieldparam conn [Rex::Proto::LDAP::Client] The LDAP connection handle to use for connecting to
182183
# the target LDAP server.
183184
# @param args [Hash] A hash containing options for the ldap connection
184185
def ldap.use_connection(args)
185186
if @open_connection
186187
yield @open_connection
188+
register_interaction
187189
else
188190
begin
189191
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 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
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

modules/auxiliary/scanner/ldap/ldap_login.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ def initialize(info = {})
3636
OptBool.new(
3737
'APPEND_DOMAIN', [true, 'Appends `@<DOMAIN> to the username for authentication`', false],
3838
conditions: ['LDAP::Auth', 'in', [Msf::Exploit::Remote::AuthOption::AUTO, Msf::Exploit::Remote::AuthOption::PLAINTEXT]]
39-
)
39+
),
40+
OptInt.new('SessionKeepalive', [true, 'Time (in seconds) for sending protocol-level keepalive messages', 10 * 60])
4041
]
4142
)
4243

@@ -48,6 +49,7 @@ def initialize(info = {})
4849
else
4950
# Don't give the option to create a session unless ldap sessions are enabled
5051
options_to_deregister << 'CreateSession'
52+
options_to_deregister << 'SessionKeepalive'
5153
end
5254

5355
deregister_options(*options_to_deregister)
@@ -175,7 +177,7 @@ def session_setup(result)
175177
return unless result.connection && result.proof
176178

177179
# Create a new session
178-
my_session = Msf::Sessions::LDAP.new(result.connection, { client: result.proof })
180+
my_session = Msf::Sessions::LDAP.new(result.connection, { client: result.proof, keepalive_seconds: datastore['SessionKeepalive'] })
179181

180182
merge_me = {
181183
'USERPASS_FILE' => nil,

spec/lib/msf/base/sessions/ldap_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
RSpec.describe Msf::Sessions::LDAP do
66
let(:client) { instance_double(Rex::Proto::LDAP::Client) }
7-
let(:opts) { { client: client } }
7+
let(:opts) { { client: client, keepalive_seconds: 600 } }
88
let(:console_class) { Rex::Post::LDAP::Ui::Console }
99
let(:user_input) { instance_double(Rex::Ui::Text::Input::Readline) }
1010
let(:user_output) { instance_double(Rex::Ui::Text::Output::Stdio) }

0 commit comments

Comments
 (0)