Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/ldap/server.rb
Views: 11704
# -*- coding: binary -*-12require 'rex/socket'3require 'net/ldap'45module Rex6module Proto7module LDAP8class Server9attr_reader :serve_udp, :serve_tcp, :sock_options, :udp_sock, :tcp_sock, :syntax, :ldif1011module LdapClient12attr_accessor :authenticated1314#15# Initialize LDAP client state16#17def init_ldap_client18self.authenticated = false19end20end2122class MockLdapClient23attr_reader :peerhost, :peerport, :srvsock2425#26# Create mock LDAP client27#28# @param host [String] PeerHost IP address29# @param port [Fixnum] PeerPort integer30# @param sock [Socket] Connection socket31def initialize(host, port, sock)32@peerhost = host33@peerport = port34@srvsock = sock35end3637#38# Test method to prevent GC/ObjectSpace abuse via class lookups39#40def mock_ldap_client?41true42end4344def write(data)45srvsock.sendto(data, peerhost, peerport)46end47end4849include Rex::IO::GramServer50#51# Create LDAP Server52#53# @param lhost [String] Listener address54# @param lport [Fixnum] Listener port55# @param udp [TrueClass, FalseClass] Listen on UDP socket56# @param tcp [TrueClass, FalseClass] Listen on TCP socket57# @param ldif [String] LDIF data58# @param auth_provider [Rex::Proto::LDAP::Auth] LDAP Authentication provider which processes authentication59# @param ctx [Hash] Framework context for sockets60# @param dblock [Proc] Handler for :dispatch_request flow control interception61# @param sblock [Proc] Handler for :send_response flow control interception62#63# @return [Rex::Proto::LDAP::Server] LDAP Server object64def initialize(lhost = '0.0.0.0', lport = 389, udp = true, tcp = true, ldif = nil, comm = nil, auth_provider = nil, ctx = {}, dblock = nil, sblock = nil)65@serve_udp = udp66@serve_tcp = tcp67@sock_options = {68'LocalHost' => lhost,69'LocalPort' => lport,70'Context' => ctx,71'Comm' => comm72}73@ldif = ldif74self.listener_thread = nil75self.dispatch_request_proc = dblock76self.send_response_proc = sblock77@auth_provider = auth_provider78end7980#81# Check if server is running82#83def running?84listener_thread and listener_thread.alive?85end8687#88# Start the LDAP server89#90def start91if serve_udp92@udp_sock = Rex::Socket::Udp.create(sock_options)93self.listener_thread = Rex::ThreadFactory.spawn('UDPLDAPServerListener', false) do94monitor_listener95end96end9798if serve_tcp99@tcp_sock = Rex::Socket::TcpServer.create(sock_options)100tcp_sock.on_client_connect_proc = proc do |cli|101on_client_connect(cli)102end103tcp_sock.on_client_data_proc = proc do |cli|104on_client_data(cli)105end106# Close UDP socket if TCP socket fails107begin108tcp_sock.start109rescue StandardError => e110stop111raise e112end113unless serve_udp114self.listener_thread = tcp_sock.listener_thread115end116end117118@auth_provider ||= Rex::Proto::LDAP::Auth.new(nil, nil, nil, nil, nil)119120self121end122123#124# Stop the LDAP server125#126def stop127ensure_close = [udp_sock, tcp_sock].compact128begin129listener_thread.kill if listener_thread.respond_to?(:kill)130self.listener_thread = nil131ensure132while csock = ensure_close.shift133csock.stop if csock.respond_to?(:stop)134csock.close unless csock.respond_to?(:close) && csock.closed?135end136end137end138139#140# Process client request, handled with dispatch_request_proc if set141#142# @param cli [Rex::Socket::Tcp, Rex::Socket::Udp] Client sending the request143# @param data [String] raw LDAP request data144def dispatch_request(cli, data)145if dispatch_request_proc146dispatch_request_proc.call(cli, data)147else148default_dispatch_request(cli, data)149end150end151152#153# Default LDAP request dispatcher154#155# @param client [Rex::Socket::Tcp, Rex::Socket::Udp] Client sending the request156# @param data [String] raw LDAP request data157def default_dispatch_request(client, data)158return if data.strip.empty? || data.strip.nil?159160processed_pdu_data = {161ip: client.peerhost,162port: client.peerport,163service_name: 'ldap',164post_pdu: false165}166167data.extend(Net::BER::Extensions::String)168begin169pdu = Net::LDAP::PDU.new(data.read_ber!(Net::LDAP::AsnSyntax))170wlog("LDAP request data remaining: #{data}") unless data.empty?171172res = case pdu.app_tag173when Net::LDAP::PDU::BindRequest174user_login = pdu.bind_parameters175server_creds = ''176context_code = nil177processed_pdu_data = @auth_provider.process_login_request(user_login).merge(processed_pdu_data)178if processed_pdu_data[:result_code] == Net::LDAP::ResultCodeSaslBindInProgress179server_creds = processed_pdu_data[:server_creds]180context_code = 7181else182processed_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"183processed_pdu_data[:result_message] += " Domain:#{processed_pdu_data[:domain]}" if processed_pdu_data[:domain]184processed_pdu_data[:post_pdu] = true185end186processed_pdu_data[:pdu_type] = pdu.app_tag187encode_ldap_response(188pdu.message_id,189processed_pdu_data[:result_code],190'',191Net::LDAP::ResultStrings[processed_pdu_data[:result_code]],192Net::LDAP::PDU::BindResult,193server_creds,194context_code195)196when Net::LDAP::PDU::SearchRequest197filter = Net::LDAP::Filter.parse_ldap_filter(pdu.search_parameters[:filter])198attrs = pdu.search_parameters[:attributes].empty? ? :all : pdu.search_parameters[:attributes]199res = search_result(filter, pdu.message_id, attrs)200if res.nil? || res.empty?201result_code = Net::LDAP::ResultCodeNoSuchObject202else203client.write(res)204result_code = Net::LDAP::ResultCodeSuccess205end206processed_pdu_data[:pdu_type] = pdu.app_tag207encode_ldap_response(208pdu.message_id,209result_code,210'',211Net::LDAP::ResultStrings[result_code],212Net::LDAP::PDU::SearchResult213)214when Net::LDAP::PDU::UnbindRequest215client.close216nil217else218if suitable_response(pdu.app_tag)219result_code = Net::LDAP::ResultCodeUnwillingToPerform220encode_ldap_response(221pdu.message_id,222result_code,223'',224Net::LDAP::ResultStrings[result_code],225suitable_response(pdu.app_tag)226)227else228client.close229end230end231232if @pdu_process[pdu.app_tag] && !processed_pdu_data.empty?233@pdu_process[pdu.app_tag].call(processed_pdu_data)234end235send_response(client, res) unless res.nil?236rescue StandardError => e237elog(e)238client.close239raise e240end241end242243#244# Encode response for LDAP client consumption245#246# @param msgid [Integer] LDAP message identifier247# @param code [Integer] LDAP message code248# @param dn [String] LDAP distinguished name249# @param msg [String] LDAP response message250# @param tag [Integer] LDAP response tag251# @param context_data [String] Additional data to serialize in the sequence252# @param context_code [Integer] Context Specific code related to `context_data`253#254# @return [Net::BER::BerIdentifiedOid] LDAP query response255def encode_ldap_response(msgid, code, dn, msg, tag, context_data = nil, context_code = nil)256tag_sequence = [257code.to_ber_enumerated,258dn.to_ber,259msg.to_ber260]261262if context_data && context_code263tag_sequence << context_data.to_ber_contextspecific(context_code)264end265266[267msgid.to_ber,268tag_sequence.to_ber_appsequence(tag)269].to_ber_sequence270end271272#273# Search provided ldif data for query information. If no `ldif` was provided a random search result will be generated.274#275# @param filter [Net::LDAP::Filter] LDAP query filter276# @param attrflt [Array, Symbol] LDAP attribute filter277#278# @return [Array] Query matches279280def search_result(filter, msgid, attrflt = :all)281if @ldif.nil? || @ldif.empty?282attrs = []283if attrflt.is_a?(Array)284attrflt.each do |at|285attrval = [Rex::Text.rand_text_alphanumeric(10)].map(&:to_ber).to_ber_set286attrs << [at.to_ber, attrval].to_ber_sequence287end288dn = "dc=#{Rex::Text.rand_text_alphanumeric(10)},dc=#{Rex::Text.rand_text_alpha(4)}"289appseq = [290dn.to_ber,291attrs.to_ber_sequence292].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)293[msgid.to_ber, appseq].to_ber_sequence294end295else296ldif.map do |bind_dn, entry|297next unless filter.match(entry)298299attrs = []300entry.each do |k, v|301if attrflt == :all || attrflt.include?(k.downcase)302attrvals = v.map(&:to_ber).to_ber_set303attrs << [k.to_ber, attrvals].to_ber_sequence304end305end306appseq = [307bind_dn.to_ber,308attrs.to_ber_sequence309].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)310[msgid.to_ber, appseq].to_ber_sequence311end.compact.join312end313end314315#316# Sets the tasks to be performed after processing of pdu object317#318# @param proc [Proc] block of code to execute319#320# @return pdu_process [Proc] steps to be executed321def processed_pdu_handler(pdu_type, &proc)322@pdu_process = []323@pdu_process[pdu_type] = proc if block_given?324end325326#327# Returns the hardcore alias for the LDAP service328#329def self.hardcore_alias(*args)330"#{args[0] || ''}-#{args[1] || ''}-#{args[4] || ''}"331end332333#334# Get suitable response for a particular request335#336# @param request [Integer] Type of request337#338# @return response [Integer] Type of response339def suitable_response(request)340responses = {341Net::LDAP::PDU::BindRequest => Net::LDAP::PDU::BindResult,342Net::LDAP::PDU::SearchRequest => Net::LDAP::PDU::SearchResult,343Net::LDAP::PDU::ModifyRequest => Net::LDAP::PDU::ModifyResponse,344Net::LDAP::PDU::AddRequest => Net::LDAP::PDU::AddResponse,345Net::LDAP::PDU::DeleteRequest => Net::LDAP::PDU::DeleteResponse,346Net::LDAP::PDU::ModifyRDNRequest => Net::LDAP::PDU::ModifyRDNResponse,347Net::LDAP::PDU::CompareRequest => Net::LDAP::PDU::CompareResponse,348Net::LDAP::PDU::ExtendedRequest => Net::LDAP::PDU::ExtendedResponse349}350351responses[request]352end353354#355# LDAP server.356#357def alias358'LDAP Server'359end360361protected362363#364# This method monitors the listener socket for new connections and calls365# the +on_client_connect+ callback routine.366#367def monitor_listener368loop do369rds = [udp_sock]370wds = []371eds = [udp_sock]372373r, = ::IO.select(rds, wds, eds, 1)374375next unless (!r.nil? && (r[0] == udp_sock))376377buf, host, port = udp_sock.recvfrom(65535)378# Mock up a client object for sending back data379cli = MockLdapClient.new(host, port, r[0])380cli.extend(LdapClient)381cli.init_ldap_client382dispatch_request(cli, buf)383end384end385386#387# Processes request coming from client388#389# @param cli [Rex::Socket::Tcp] Client sending request390def on_client_data(cli)391data = cli.read(65535)392raise ::EOFError if !data393raise ::EOFError if data.empty?394395dispatch_request(cli, data)396rescue EOFError => e397tcp_sock.close_client(cli) if cli398raise e399end400401#402# Extend client for LDAP state403#404def on_client_connect(cli)405cli.extend(LdapClient)406cli.init_ldap_client407end408409end410end411end412end413414415