CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/ldap/server.rb
Views: 11704
1
# -*- coding: binary -*-
2
3
require 'rex/socket'
4
require 'net/ldap'
5
6
module Rex
7
module Proto
8
module LDAP
9
class Server
10
attr_reader :serve_udp, :serve_tcp, :sock_options, :udp_sock, :tcp_sock, :syntax, :ldif
11
12
module LdapClient
13
attr_accessor :authenticated
14
15
#
16
# Initialize LDAP client state
17
#
18
def init_ldap_client
19
self.authenticated = false
20
end
21
end
22
23
class MockLdapClient
24
attr_reader :peerhost, :peerport, :srvsock
25
26
#
27
# Create mock LDAP client
28
#
29
# @param host [String] PeerHost IP address
30
# @param port [Fixnum] PeerPort integer
31
# @param sock [Socket] Connection socket
32
def initialize(host, port, sock)
33
@peerhost = host
34
@peerport = port
35
@srvsock = sock
36
end
37
38
#
39
# Test method to prevent GC/ObjectSpace abuse via class lookups
40
#
41
def mock_ldap_client?
42
true
43
end
44
45
def write(data)
46
srvsock.sendto(data, peerhost, peerport)
47
end
48
end
49
50
include Rex::IO::GramServer
51
#
52
# Create LDAP Server
53
#
54
# @param lhost [String] Listener address
55
# @param lport [Fixnum] Listener port
56
# @param udp [TrueClass, FalseClass] Listen on UDP socket
57
# @param tcp [TrueClass, FalseClass] Listen on TCP socket
58
# @param ldif [String] LDIF data
59
# @param auth_provider [Rex::Proto::LDAP::Auth] LDAP Authentication provider which processes authentication
60
# @param ctx [Hash] Framework context for sockets
61
# @param dblock [Proc] Handler for :dispatch_request flow control interception
62
# @param sblock [Proc] Handler for :send_response flow control interception
63
#
64
# @return [Rex::Proto::LDAP::Server] LDAP Server object
65
def initialize(lhost = '0.0.0.0', lport = 389, udp = true, tcp = true, ldif = nil, comm = nil, auth_provider = nil, ctx = {}, dblock = nil, sblock = nil)
66
@serve_udp = udp
67
@serve_tcp = tcp
68
@sock_options = {
69
'LocalHost' => lhost,
70
'LocalPort' => lport,
71
'Context' => ctx,
72
'Comm' => comm
73
}
74
@ldif = ldif
75
self.listener_thread = nil
76
self.dispatch_request_proc = dblock
77
self.send_response_proc = sblock
78
@auth_provider = auth_provider
79
end
80
81
#
82
# Check if server is running
83
#
84
def running?
85
listener_thread and listener_thread.alive?
86
end
87
88
#
89
# Start the LDAP server
90
#
91
def start
92
if serve_udp
93
@udp_sock = Rex::Socket::Udp.create(sock_options)
94
self.listener_thread = Rex::ThreadFactory.spawn('UDPLDAPServerListener', false) do
95
monitor_listener
96
end
97
end
98
99
if serve_tcp
100
@tcp_sock = Rex::Socket::TcpServer.create(sock_options)
101
tcp_sock.on_client_connect_proc = proc do |cli|
102
on_client_connect(cli)
103
end
104
tcp_sock.on_client_data_proc = proc do |cli|
105
on_client_data(cli)
106
end
107
# Close UDP socket if TCP socket fails
108
begin
109
tcp_sock.start
110
rescue StandardError => e
111
stop
112
raise e
113
end
114
unless serve_udp
115
self.listener_thread = tcp_sock.listener_thread
116
end
117
end
118
119
@auth_provider ||= Rex::Proto::LDAP::Auth.new(nil, nil, nil, nil, nil)
120
121
self
122
end
123
124
#
125
# Stop the LDAP server
126
#
127
def stop
128
ensure_close = [udp_sock, tcp_sock].compact
129
begin
130
listener_thread.kill if listener_thread.respond_to?(:kill)
131
self.listener_thread = nil
132
ensure
133
while csock = ensure_close.shift
134
csock.stop if csock.respond_to?(:stop)
135
csock.close unless csock.respond_to?(:close) && csock.closed?
136
end
137
end
138
end
139
140
#
141
# Process client request, handled with dispatch_request_proc if set
142
#
143
# @param cli [Rex::Socket::Tcp, Rex::Socket::Udp] Client sending the request
144
# @param data [String] raw LDAP request data
145
def dispatch_request(cli, data)
146
if dispatch_request_proc
147
dispatch_request_proc.call(cli, data)
148
else
149
default_dispatch_request(cli, data)
150
end
151
end
152
153
#
154
# Default LDAP request dispatcher
155
#
156
# @param client [Rex::Socket::Tcp, Rex::Socket::Udp] Client sending the request
157
# @param data [String] raw LDAP request data
158
def default_dispatch_request(client, data)
159
return if data.strip.empty? || data.strip.nil?
160
161
processed_pdu_data = {
162
ip: client.peerhost,
163
port: client.peerport,
164
service_name: 'ldap',
165
post_pdu: false
166
}
167
168
data.extend(Net::BER::Extensions::String)
169
begin
170
pdu = Net::LDAP::PDU.new(data.read_ber!(Net::LDAP::AsnSyntax))
171
wlog("LDAP request data remaining: #{data}") unless data.empty?
172
173
res = case pdu.app_tag
174
when Net::LDAP::PDU::BindRequest
175
user_login = pdu.bind_parameters
176
server_creds = ''
177
context_code = nil
178
processed_pdu_data = @auth_provider.process_login_request(user_login).merge(processed_pdu_data)
179
if processed_pdu_data[:result_code] == Net::LDAP::ResultCodeSaslBindInProgress
180
server_creds = processed_pdu_data[:server_creds]
181
context_code = 7
182
else
183
processed_pdu_data[:result_message] = "LDAP Login Attempt => From:#{processed_pdu_data[:ip]}:#{processed_pdu_data[:port]}\t Username:#{processed_pdu_data[:user]}\t #{processed_pdu_data[:private_type]}:#{processed_pdu_data[:private]}\t"
184
processed_pdu_data[:result_message] += " Domain:#{processed_pdu_data[:domain]}" if processed_pdu_data[:domain]
185
processed_pdu_data[:post_pdu] = true
186
end
187
processed_pdu_data[:pdu_type] = pdu.app_tag
188
encode_ldap_response(
189
pdu.message_id,
190
processed_pdu_data[:result_code],
191
'',
192
Net::LDAP::ResultStrings[processed_pdu_data[:result_code]],
193
Net::LDAP::PDU::BindResult,
194
server_creds,
195
context_code
196
)
197
when Net::LDAP::PDU::SearchRequest
198
filter = Net::LDAP::Filter.parse_ldap_filter(pdu.search_parameters[:filter])
199
attrs = pdu.search_parameters[:attributes].empty? ? :all : pdu.search_parameters[:attributes]
200
res = search_result(filter, pdu.message_id, attrs)
201
if res.nil? || res.empty?
202
result_code = Net::LDAP::ResultCodeNoSuchObject
203
else
204
client.write(res)
205
result_code = Net::LDAP::ResultCodeSuccess
206
end
207
processed_pdu_data[:pdu_type] = pdu.app_tag
208
encode_ldap_response(
209
pdu.message_id,
210
result_code,
211
'',
212
Net::LDAP::ResultStrings[result_code],
213
Net::LDAP::PDU::SearchResult
214
)
215
when Net::LDAP::PDU::UnbindRequest
216
client.close
217
nil
218
else
219
if suitable_response(pdu.app_tag)
220
result_code = Net::LDAP::ResultCodeUnwillingToPerform
221
encode_ldap_response(
222
pdu.message_id,
223
result_code,
224
'',
225
Net::LDAP::ResultStrings[result_code],
226
suitable_response(pdu.app_tag)
227
)
228
else
229
client.close
230
end
231
end
232
233
if @pdu_process[pdu.app_tag] && !processed_pdu_data.empty?
234
@pdu_process[pdu.app_tag].call(processed_pdu_data)
235
end
236
send_response(client, res) unless res.nil?
237
rescue StandardError => e
238
elog(e)
239
client.close
240
raise e
241
end
242
end
243
244
#
245
# Encode response for LDAP client consumption
246
#
247
# @param msgid [Integer] LDAP message identifier
248
# @param code [Integer] LDAP message code
249
# @param dn [String] LDAP distinguished name
250
# @param msg [String] LDAP response message
251
# @param tag [Integer] LDAP response tag
252
# @param context_data [String] Additional data to serialize in the sequence
253
# @param context_code [Integer] Context Specific code related to `context_data`
254
#
255
# @return [Net::BER::BerIdentifiedOid] LDAP query response
256
def encode_ldap_response(msgid, code, dn, msg, tag, context_data = nil, context_code = nil)
257
tag_sequence = [
258
code.to_ber_enumerated,
259
dn.to_ber,
260
msg.to_ber
261
]
262
263
if context_data && context_code
264
tag_sequence << context_data.to_ber_contextspecific(context_code)
265
end
266
267
[
268
msgid.to_ber,
269
tag_sequence.to_ber_appsequence(tag)
270
].to_ber_sequence
271
end
272
273
#
274
# Search provided ldif data for query information. If no `ldif` was provided a random search result will be generated.
275
#
276
# @param filter [Net::LDAP::Filter] LDAP query filter
277
# @param attrflt [Array, Symbol] LDAP attribute filter
278
#
279
# @return [Array] Query matches
280
281
def search_result(filter, msgid, attrflt = :all)
282
if @ldif.nil? || @ldif.empty?
283
attrs = []
284
if attrflt.is_a?(Array)
285
attrflt.each do |at|
286
attrval = [Rex::Text.rand_text_alphanumeric(10)].map(&:to_ber).to_ber_set
287
attrs << [at.to_ber, attrval].to_ber_sequence
288
end
289
dn = "dc=#{Rex::Text.rand_text_alphanumeric(10)},dc=#{Rex::Text.rand_text_alpha(4)}"
290
appseq = [
291
dn.to_ber,
292
attrs.to_ber_sequence
293
].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)
294
[msgid.to_ber, appseq].to_ber_sequence
295
end
296
else
297
ldif.map do |bind_dn, entry|
298
next unless filter.match(entry)
299
300
attrs = []
301
entry.each do |k, v|
302
if attrflt == :all || attrflt.include?(k.downcase)
303
attrvals = v.map(&:to_ber).to_ber_set
304
attrs << [k.to_ber, attrvals].to_ber_sequence
305
end
306
end
307
appseq = [
308
bind_dn.to_ber,
309
attrs.to_ber_sequence
310
].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)
311
[msgid.to_ber, appseq].to_ber_sequence
312
end.compact.join
313
end
314
end
315
316
#
317
# Sets the tasks to be performed after processing of pdu object
318
#
319
# @param proc [Proc] block of code to execute
320
#
321
# @return pdu_process [Proc] steps to be executed
322
def processed_pdu_handler(pdu_type, &proc)
323
@pdu_process = []
324
@pdu_process[pdu_type] = proc if block_given?
325
end
326
327
#
328
# Returns the hardcore alias for the LDAP service
329
#
330
def self.hardcore_alias(*args)
331
"#{args[0] || ''}-#{args[1] || ''}-#{args[4] || ''}"
332
end
333
334
#
335
# Get suitable response for a particular request
336
#
337
# @param request [Integer] Type of request
338
#
339
# @return response [Integer] Type of response
340
def suitable_response(request)
341
responses = {
342
Net::LDAP::PDU::BindRequest => Net::LDAP::PDU::BindResult,
343
Net::LDAP::PDU::SearchRequest => Net::LDAP::PDU::SearchResult,
344
Net::LDAP::PDU::ModifyRequest => Net::LDAP::PDU::ModifyResponse,
345
Net::LDAP::PDU::AddRequest => Net::LDAP::PDU::AddResponse,
346
Net::LDAP::PDU::DeleteRequest => Net::LDAP::PDU::DeleteResponse,
347
Net::LDAP::PDU::ModifyRDNRequest => Net::LDAP::PDU::ModifyRDNResponse,
348
Net::LDAP::PDU::CompareRequest => Net::LDAP::PDU::CompareResponse,
349
Net::LDAP::PDU::ExtendedRequest => Net::LDAP::PDU::ExtendedResponse
350
}
351
352
responses[request]
353
end
354
355
#
356
# LDAP server.
357
#
358
def alias
359
'LDAP Server'
360
end
361
362
protected
363
364
#
365
# This method monitors the listener socket for new connections and calls
366
# the +on_client_connect+ callback routine.
367
#
368
def monitor_listener
369
loop do
370
rds = [udp_sock]
371
wds = []
372
eds = [udp_sock]
373
374
r, = ::IO.select(rds, wds, eds, 1)
375
376
next unless (!r.nil? && (r[0] == udp_sock))
377
378
buf, host, port = udp_sock.recvfrom(65535)
379
# Mock up a client object for sending back data
380
cli = MockLdapClient.new(host, port, r[0])
381
cli.extend(LdapClient)
382
cli.init_ldap_client
383
dispatch_request(cli, buf)
384
end
385
end
386
387
#
388
# Processes request coming from client
389
#
390
# @param cli [Rex::Socket::Tcp] Client sending request
391
def on_client_data(cli)
392
data = cli.read(65535)
393
raise ::EOFError if !data
394
raise ::EOFError if data.empty?
395
396
dispatch_request(cli, data)
397
rescue EOFError => e
398
tcp_sock.close_client(cli) if cli
399
raise e
400
end
401
402
#
403
# Extend client for LDAP state
404
#
405
def on_client_connect(cli)
406
cli.extend(LdapClient)
407
cli.init_ldap_client
408
end
409
410
end
411
end
412
end
413
end
414
415