Class: Rex::Proto::LDAP::Server

Inherits:
Object
  • Object
show all
Includes:
IO::GramServer
Defined in:
lib/rex/proto/ldap/server.rb

Defined Under Namespace

Modules: LdapClient Classes: MockLdapClient

Instance Attribute Summary collapse

Attributes included from IO::GramServer

#dispatch_request_proc, #listener_thread, #send_response_proc

Class Method Summary collapse

Instance Method Summary collapse

Methods included from IO::GramServer

#send_response, #wait

Constructor Details

#initialize(lhost = '0.0.0.0', lport = 389, udp = true, tcp = true, ldif = nil, comm = nil, auth_provider = nil, ctx = {}, dblock = nil, sblock = nil) ⇒ Rex::Proto::LDAP::Server

Create LDAP Server

Parameters:

  • lhost (String) (defaults to: '0.0.0.0')

    Listener address

  • lport (Fixnum) (defaults to: 389)

    Listener port

  • udp (TrueClass, FalseClass) (defaults to: true)

    Listen on UDP socket

  • tcp (TrueClass, FalseClass) (defaults to: true)

    Listen on TCP socket

  • ldif (String) (defaults to: nil)

    LDIF data

  • auth_provider (Rex::Proto::LDAP::Auth) (defaults to: nil)

    LDAP Authentication provider which processes authentication

  • ctx (Hash) (defaults to: {})

    Framework context for sockets

  • dblock (Proc) (defaults to: nil)

    Handler for :dispatch_request flow control interception

  • sblock (Proc) (defaults to: nil)

    Handler for :send_response flow control interception



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/rex/proto/ldap/server.rb', line 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)
  @serve_udp = udp
  @serve_tcp = tcp
  @sock_options = {
    'LocalHost' => lhost,
    'LocalPort' => lport,
    'Context' => ctx,
    'Comm' => comm
  }
  @ldif = ldif
  self.listener_thread = nil
  self.dispatch_request_proc = dblock
  self.send_response_proc = sblock
  @auth_provider  = auth_provider
end

Instance Attribute Details

#ldifObject (readonly)

Returns the value of attribute ldif.



10
11
12
# File 'lib/rex/proto/ldap/server.rb', line 10

def ldif
  @ldif
end

#serve_tcpObject (readonly)

Returns the value of attribute serve_tcp.



10
11
12
# File 'lib/rex/proto/ldap/server.rb', line 10

def serve_tcp
  @serve_tcp
end

#serve_udpObject (readonly)

Returns the value of attribute serve_udp.



10
11
12
# File 'lib/rex/proto/ldap/server.rb', line 10

def serve_udp
  @serve_udp
end

#sock_optionsObject (readonly)

Returns the value of attribute sock_options.



10
11
12
# File 'lib/rex/proto/ldap/server.rb', line 10

def sock_options
  @sock_options
end

#syntaxObject (readonly)

Returns the value of attribute syntax.



10
11
12
# File 'lib/rex/proto/ldap/server.rb', line 10

def syntax
  @syntax
end

#tcp_sockObject (readonly)

Returns the value of attribute tcp_sock.



10
11
12
# File 'lib/rex/proto/ldap/server.rb', line 10

def tcp_sock
  @tcp_sock
end

#udp_sockObject (readonly)

Returns the value of attribute udp_sock.



10
11
12
# File 'lib/rex/proto/ldap/server.rb', line 10

def udp_sock
  @udp_sock
end

Class Method Details

.hardcore_alias(*args) ⇒ Object

Returns the hardcore alias for the LDAP service



330
331
332
# File 'lib/rex/proto/ldap/server.rb', line 330

def self.hardcore_alias(*args)
  "#{args[0] || ''}-#{args[1] || ''}-#{args[4] || ''}"
end

Instance Method Details

#aliasObject

LDAP server.



358
359
360
# File 'lib/rex/proto/ldap/server.rb', line 358

def alias
  'LDAP Server'
end

#default_dispatch_request(client, data) ⇒ Object

Default LDAP request dispatcher

Parameters:

  • client (Rex::Socket::Tcp, Rex::Socket::Udp)

    Client sending the request

  • data (String)

    raw LDAP request data



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/rex/proto/ldap/server.rb', line 158

def default_dispatch_request(client, data)
  return if data.strip.empty? || data.strip.nil?

  processed_pdu_data = {
    ip: client.peerhost,
    port: client.peerport,
    service_name: 'ldap',
    post_pdu: false
  }

  data.extend(Net::BER::Extensions::String)
  begin
    pdu = Net::LDAP::PDU.new(data.read_ber!(Net::LDAP::AsnSyntax))
    wlog("LDAP request data remaining: #{data}") unless data.empty?

    res = case pdu.app_tag
          when Net::LDAP::PDU::BindRequest
             = pdu.bind_parameters
            server_creds = ''
            context_code = nil
            processed_pdu_data = @auth_provider.().merge(processed_pdu_data)
            if processed_pdu_data[:result_code] == Net::LDAP::ResultCodeSaslBindInProgress
              server_creds = processed_pdu_data[:server_creds]
              context_code = 7
            else
              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"
              processed_pdu_data[:result_message] += " Domain:#{processed_pdu_data[:domain]}" if processed_pdu_data[:domain]
              processed_pdu_data[:post_pdu] = true
            end
            processed_pdu_data[:pdu_type] = pdu.app_tag
            encode_ldap_response(
              pdu.message_id,
              processed_pdu_data[:result_code],
              '',
              Net::LDAP::ResultStrings[processed_pdu_data[:result_code]],
              Net::LDAP::PDU::BindResult,
              server_creds,
              context_code
            )
          when Net::LDAP::PDU::SearchRequest
            filter = Net::LDAP::Filter.parse_ldap_filter(pdu.search_parameters[:filter])
            attrs = pdu.search_parameters[:attributes].empty? ? :all : pdu.search_parameters[:attributes]
            res = search_result(filter, pdu.message_id, attrs)
            if res.nil? || res.empty?
              result_code = Net::LDAP::ResultCodeNoSuchObject
            else
              client.write(res)
              result_code = Net::LDAP::ResultCodeSuccess
            end
            processed_pdu_data[:pdu_type] = pdu.app_tag
            encode_ldap_response(
              pdu.message_id,
              result_code,
              '',
              Net::LDAP::ResultStrings[result_code],
              Net::LDAP::PDU::SearchResult
            )
          when Net::LDAP::PDU::UnbindRequest
            client.close
            nil
          else
            if suitable_response(pdu.app_tag)
              result_code = Net::LDAP::ResultCodeUnwillingToPerform
              encode_ldap_response(
                pdu.message_id,
                result_code,
                '',
                Net::LDAP::ResultStrings[result_code],
                suitable_response(pdu.app_tag)
              )
            else
              client.close
            end
          end

    if @pdu_process[pdu.app_tag] && !processed_pdu_data.empty?
      @pdu_process[pdu.app_tag].call(processed_pdu_data)
    end
    send_response(client, res) unless res.nil?
  rescue StandardError => e
    elog(e)
    client.close
    raise e
  end
end

#dispatch_request(cli, data) ⇒ Object

Process client request, handled with dispatch_request_proc if set

Parameters:

  • cli (Rex::Socket::Tcp, Rex::Socket::Udp)

    Client sending the request

  • data (String)

    raw LDAP request data



145
146
147
148
149
150
151
# File 'lib/rex/proto/ldap/server.rb', line 145

def dispatch_request(cli, data)
  if dispatch_request_proc
    dispatch_request_proc.call(cli, data)
  else
    default_dispatch_request(cli, data)
  end
end

#encode_ldap_response(msgid, code, dn, msg, tag, context_data = nil, context_code = nil) ⇒ Net::BER::BerIdentifiedOid

Encode response for LDAP client consumption

Parameters:

  • msgid (Integer)

    LDAP message identifier

  • code (Integer)

    LDAP message code

  • dn (String)

    LDAP distinguished name

  • msg (String)

    LDAP response message

  • tag (Integer)

    LDAP response tag

  • context_data (String) (defaults to: nil)

    Additional data to serialize in the sequence

  • context_code (Integer) (defaults to: nil)

    Context Specific code related to 'context_data`

Returns:

  • (Net::BER::BerIdentifiedOid)

    LDAP query response



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/rex/proto/ldap/server.rb', line 256

def encode_ldap_response(msgid, code, dn, msg, tag, context_data = nil, context_code = nil)
  tag_sequence = [
    code.to_ber_enumerated,
    dn.to_ber,
    msg.to_ber
  ]

  if context_data && context_code
    tag_sequence << context_data.to_ber_contextspecific(context_code)
  end

  [
    msgid.to_ber,
    tag_sequence.to_ber_appsequence(tag)
  ].to_ber_sequence
end

#monitor_listenerObject (protected)

This method monitors the listener socket for new connections and calls the on_client_connect callback routine.



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# File 'lib/rex/proto/ldap/server.rb', line 368

def monitor_listener
  loop do
    rds = [udp_sock]
    wds = []
    eds = [udp_sock]

    r, = ::IO.select(rds, wds, eds, 1)

    next unless (!r.nil? && (r[0] == udp_sock))

    buf, host, port = udp_sock.recvfrom(65535)
    # Mock up a client object for sending back data
    cli = MockLdapClient.new(host, port, r[0])
    cli.extend(LdapClient)
    cli.init_ldap_client
    dispatch_request(cli, buf)
  end
end

#on_client_connect(cli) ⇒ Object (protected)

Extend client for LDAP state



405
406
407
408
# File 'lib/rex/proto/ldap/server.rb', line 405

def on_client_connect(cli)
  cli.extend(LdapClient)
  cli.init_ldap_client
end

#on_client_data(cli) ⇒ Object (protected)

Processes request coming from client

Parameters:

  • cli (Rex::Socket::Tcp)

    Client sending request



391
392
393
394
395
396
397
398
399
400
# File 'lib/rex/proto/ldap/server.rb', line 391

def on_client_data(cli)
  data = cli.read(65535)
  raise ::EOFError if !data
  raise ::EOFError if data.empty?

  dispatch_request(cli, data)
rescue EOFError => e
  tcp_sock.close_client(cli) if cli
  raise e
end

#processed_pdu_handler(pdu_type, &proc) ⇒ Object

Sets the tasks to be performed after processing of pdu object

Parameters:

  • proc (Proc)

    block of code to execute



322
323
324
325
# File 'lib/rex/proto/ldap/server.rb', line 322

def processed_pdu_handler(pdu_type, &proc)
  @pdu_process = []
  @pdu_process[pdu_type] = proc if block_given?
end

#running?Boolean

Check if server is running

Returns:

  • (Boolean)


84
85
86
# File 'lib/rex/proto/ldap/server.rb', line 84

def running?
  listener_thread and listener_thread.alive?
end

#search_result(filter, msgid, attrflt = :all) ⇒ Array

Search provided ldif data for query information. If no ‘ldif` was provided a random search result will be generated.

Parameters:

  • filter (Net::LDAP::Filter)

    LDAP query filter

  • attrflt (Array, Symbol) (defaults to: :all)

    LDAP attribute filter

Returns:

  • (Array)

    Query matches



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/rex/proto/ldap/server.rb', line 281

def search_result(filter, msgid, attrflt = :all)
  if @ldif.nil? || @ldif.empty?
    attrs = []
    if attrflt.is_a?(Array)
      attrflt.each do |at|
        attrval = [Rex::Text.rand_text_alphanumeric(10)].map(&:to_ber).to_ber_set
        attrs << [at.to_ber, attrval].to_ber_sequence
      end
      dn = "dc=#{Rex::Text.rand_text_alphanumeric(10)},dc=#{Rex::Text.rand_text_alpha(4)}"
      appseq = [
        dn.to_ber,
        attrs.to_ber_sequence
      ].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)
      [msgid.to_ber, appseq].to_ber_sequence
    end
  else
    ldif.map do |bind_dn, entry|
      next unless filter.match(entry)

      attrs = []
      entry.each do |k, v|
        if attrflt == :all || attrflt.include?(k.downcase)
          attrvals = v.map(&:to_ber).to_ber_set
          attrs << [k.to_ber, attrvals].to_ber_sequence
        end
      end
      appseq = [
        bind_dn.to_ber,
        attrs.to_ber_sequence
      ].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)
      [msgid.to_ber, appseq].to_ber_sequence
    end.compact.join
  end
end

#startObject

Start the LDAP server



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/rex/proto/ldap/server.rb', line 91

def start
  if serve_udp
    @udp_sock = Rex::Socket::Udp.create(sock_options)
    self.listener_thread = Rex::ThreadFactory.spawn('UDPLDAPServerListener', false) do
      monitor_listener
    end
  end

  if serve_tcp
    @tcp_sock = Rex::Socket::TcpServer.create(sock_options)
    tcp_sock.on_client_connect_proc = proc do |cli|
      on_client_connect(cli)
    end
    tcp_sock.on_client_data_proc = proc do |cli|
      on_client_data(cli)
    end
    # Close UDP socket if TCP socket fails
    begin
      tcp_sock.start
    rescue StandardError => e
      stop
      raise e
    end
    unless serve_udp
      self.listener_thread = tcp_sock.listener_thread
    end
  end

  @auth_provider ||= Rex::Proto::LDAP::Auth.new(nil, nil, nil, nil, nil)

  self
end

#stopObject

Stop the LDAP server



127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/rex/proto/ldap/server.rb', line 127

def stop
  ensure_close = [udp_sock, tcp_sock].compact
  begin
    listener_thread.kill if listener_thread.respond_to?(:kill)
    self.listener_thread = nil
  ensure
    while csock = ensure_close.shift
      csock.stop if csock.respond_to?(:stop)
      csock.close unless csock.respond_to?(:close) && csock.closed?
    end
  end
end

#suitable_response(request) ⇒ Object

Get suitable response for a particular request

Parameters:

  • request (Integer)

    Type of request



340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/rex/proto/ldap/server.rb', line 340

def suitable_response(request)
  responses = {
    Net::LDAP::PDU::BindRequest => Net::LDAP::PDU::BindResult,
    Net::LDAP::PDU::SearchRequest => Net::LDAP::PDU::SearchResult,
    Net::LDAP::PDU::ModifyRequest => Net::LDAP::PDU::ModifyResponse,
    Net::LDAP::PDU::AddRequest => Net::LDAP::PDU::AddResponse,
    Net::LDAP::PDU::DeleteRequest => Net::LDAP::PDU::DeleteResponse,
    Net::LDAP::PDU::ModifyRDNRequest => Net::LDAP::PDU::ModifyRDNResponse,
    Net::LDAP::PDU::CompareRequest => Net::LDAP::PDU::CompareResponse,
    Net::LDAP::PDU::ExtendedRequest => Net::LDAP::PDU::ExtendedResponse
  }

  responses[request]
end