Skip to content

Commit f3a8b35

Browse files
committed
Land rapid7#19058, Add new Ldap session type
Merge branch 'land-19058' into upstream-master
2 parents f6e7aac + 68f7334 commit f3a8b35

File tree

40 files changed

+1327
-305
lines changed

40 files changed

+1327
-305
lines changed

lib/metasploit/framework/login_scanner/ldap.rb

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ class LDAP
1111
include Metasploit::Framework::LDAP::Client
1212
include Msf::Exploit::Remote::LDAP
1313

14-
attr_accessor :opts
15-
attr_accessor :realm_key
14+
attr_accessor :opts, :realm_key
15+
# @!attribute use_client_as_proof
16+
# @return [Boolean] If a login is successful and this attribute is true - an LDAP::Client instance is used as proof
17+
attr_accessor :use_client_as_proof
1618

1719
def attempt_login(credential)
1820
result_opts = {
@@ -36,17 +38,24 @@ def do_login(credential)
3638
}.merge(@opts)
3739

3840
connect_opts = ldap_connect_opts(host, port, connection_timeout, ssl: opts[:ssl], opts: opts)
39-
ldap_open(connect_opts) do |ldap|
40-
return status_code(ldap.get_operation_result.table)
41+
begin
42+
ldap_client = ldap_open(connect_opts, keep_open: true)
43+
return status_code(ldap_client)
4144
rescue StandardError => e
4245
{ status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e }
4346
end
4447
end
4548

46-
def status_code(operation_result)
47-
case operation_result[:code]
49+
def status_code(ldap_client)
50+
operation_result = ldap_client.get_operation_result.table[:code]
51+
case operation_result
4852
when 0
49-
{ status: Metasploit::Model::Login::Status::SUCCESSFUL }
53+
result = { status: Metasploit::Model::Login::Status::SUCCESSFUL }
54+
if use_client_as_proof
55+
result[:proof] = ldap_client
56+
result[:connection] = ldap_client.socket
57+
end
58+
result
5059
else
5160
{ status: Metasploit::Model::Login::Status::INCORRECT, proof: "Bind Result: #{operation_result}" }
5261
end
@@ -84,7 +93,6 @@ def each_credential
8493
credential.public = "#{credential.public}@#{opts[:domain]}"
8594
yield credential
8695
end
87-
8896
end
8997
end
9098
end

lib/msf/base/config.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ def self.smb_session_history
221221
self.new.smb_session_history
222222
end
223223

224+
# Returns the full path to the ldap session history file.
225+
#
226+
# @return [String] path to the history file.
227+
def self.ldap_session_history
228+
self.new.ldap_session_history
229+
end
230+
224231
# Returns the full path to the PostgreSQL session history file.
225232
#
226233
# @return [String] path to the history file.
@@ -351,6 +358,10 @@ def smb_session_history
351358
config_directory + FileSep + "smb_session_history"
352359
end
353360

361+
def ldap_session_history
362+
config_directory + FileSep + "ldap_session_history"
363+
end
364+
354365
def postgresql_session_history
355366
config_directory + FileSep + "postgresql_session_history"
356367
end

lib/msf/base/sessions/ldap.rb

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# -*- coding: binary -*-
2+
3+
require 'rex/post/ldap'
4+
5+
class Msf::Sessions::LDAP
6+
#
7+
# This interface supports basic interaction.
8+
#
9+
include Msf::Session::Basic
10+
include Msf::Sessions::Scriptable
11+
12+
# @return [Rex::Post::LDAP::Ui::Console] The interactive console
13+
attr_accessor :console
14+
# @return [Rex::Proto::LDAP::Client] The LDAP client
15+
attr_accessor :client
16+
17+
attr_accessor :platform, :arch
18+
attr_reader :framework
19+
20+
# @param[Rex::IO::Stream] rstream
21+
# @param [Hash] opts
22+
# @option opts [Rex::Proto::LDAP::Client] :client
23+
def initialize(rstream, opts = {})
24+
@client = opts.fetch(:client)
25+
self.console = Rex::Post::LDAP::Ui::Console.new(self)
26+
super(rstream, opts)
27+
end
28+
29+
def bootstrap(datastore = {}, handler = nil)
30+
session = self
31+
session.init_ui(user_input, user_output)
32+
33+
@info = "LDAP #{datastore['USERNAME']} @ #{@peer_info}"
34+
end
35+
36+
def execute_file(full_path, args)
37+
if File.extname(full_path) == '.rb'
38+
Rex::Script::Shell.new(self, full_path).run(args)
39+
else
40+
console.load_resource(full_path)
41+
end
42+
end
43+
44+
def process_autoruns(datastore)
45+
['InitialAutoRunScript', 'AutoRunScript'].each do |key|
46+
next if datastore[key].nil? || datastore[key].empty?
47+
48+
args = Shellwords.shellwords(datastore[key])
49+
print_status("Session ID #{sid} (#{tunnel_to_s}) processing #{key} '#{datastore[key]}'")
50+
execute_script(args.shift, *args)
51+
end
52+
end
53+
54+
def type
55+
self.class.type
56+
end
57+
58+
# Returns the type of session.
59+
#
60+
def self.type
61+
'ldap'
62+
end
63+
64+
def self.can_cleanup_files
65+
false
66+
end
67+
68+
#
69+
# Returns the session description.
70+
#
71+
def desc
72+
'LDAP'
73+
end
74+
75+
def address
76+
@address ||= client.peerhost
77+
end
78+
79+
def port
80+
@port ||= client.peerport
81+
end
82+
83+
##
84+
# :category: Msf::Session::Interactive implementors
85+
#
86+
# Initializes the console's I/O handles.
87+
#
88+
def init_ui(input, output)
89+
self.user_input = input
90+
self.user_output = output
91+
console.init_ui(input, output)
92+
console.set_log_source(log_source)
93+
94+
super
95+
end
96+
97+
##
98+
# :category: Msf::Session::Interactive implementors
99+
#
100+
# Resets the console's I/O handles.
101+
#
102+
def reset_ui
103+
console.unset_log_source
104+
console.reset_ui
105+
end
106+
107+
def exit
108+
console.stop
109+
end
110+
111+
##
112+
# :category: Msf::Session::Interactive implementors
113+
#
114+
# Override the basic session interaction to use shell_read and
115+
# shell_write instead of operating on rstream directly.
116+
def _interact
117+
framework.events.on_session_interact(self)
118+
framework.history_manager.with_context(name: type.to_sym) do
119+
_interact_stream
120+
end
121+
end
122+
123+
##
124+
# :category: Msf::Session::Interactive implementors
125+
#
126+
def _interact_stream
127+
framework.events.on_session_interact(self)
128+
129+
console.framework = framework
130+
# Call the console interaction of the ldap client and
131+
# pass it a block that returns whether or not we should still be
132+
# interacting. This will allow the shell to abort if interaction is
133+
# canceled.
134+
console.interact { interacting != true }
135+
console.framework = nil
136+
137+
# If the stop flag has been set, then that means the user exited. Raise
138+
# the EOFError so we can drop this handle like a bad habit.
139+
raise EOFError if (console.stopped? == true)
140+
end
141+
142+
end

lib/msf/core/exploit/remote/kerberos/client.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module Client
2727

2828
# @!attribute client
2929
# @return [Rex::Proto::Kerberos::Client] The kerberos client
30-
attr_accessor :client
30+
attr_accessor :kerberos_client
3131

3232
def initialize(info = {})
3333
super
@@ -96,20 +96,20 @@ def connect(opts={})
9696
protocol: 'tcp'
9797
)
9898

99-
disconnect if client
100-
self.client = kerb_client
99+
disconnect if kerberos_client
100+
self.kerberos_client = kerb_client
101101

102102
kerb_client
103103
end
104104

105105
# Disconnects the Kerberos client
106106
#
107107
# @param kerb_client [Rex::Proto::Kerberos::Client] the client to disconnect
108-
def disconnect(kerb_client = client)
108+
def disconnect(kerb_client = kerberos_client)
109109
kerb_client.close if kerb_client
110110

111-
if kerb_client == client
112-
self.client = nil
111+
if kerb_client == kerberos_client
112+
self.kerberos_client = nil
113113
end
114114
end
115115

@@ -129,7 +129,7 @@ def cleanup
129129
def send_request_as(opts = {})
130130
connect(opts)
131131
req = opts.fetch(:req) { build_as_request(opts) }
132-
res = client.send_recv(req)
132+
res = kerberos_client.send_recv(req)
133133
disconnect
134134
res
135135
end
@@ -143,7 +143,7 @@ def send_request_as(opts = {})
143143
def send_request_tgs(opts = {})
144144
connect(opts)
145145
req = opts.fetch(:req) { build_tgs_request(opts) }
146-
res = client.send_recv(req)
146+
res = kerberos_client.send_recv(req)
147147
disconnect
148148
res
149149
end

0 commit comments

Comments
 (0)