Path: blob/master/lib/rex/proto/ldap/client.rb
52178 views
require 'net/ldap'12module Rex3module Proto4module LDAP5# This is a Rex Proto wrapper around the Net::LDAP client which is currently coming from the 'net-ldap' gem.6# The purpose of this wrapper is to provide 'peerhost' and 'peerport' methods to ensure the client interfaces7# are consistent between various session clients.8class Client < Net::LDAP910# @return [Rex::Socket]11attr_reader :socket1213# [Time] The last time an interaction occurred on the connection (for keep-alive purposes)14attr_reader :last_interaction1516# [Mutex] Control access to the connection. One at a time.17attr_reader :connection_use_mutex1819def initialize(args)20@base_dn = args[:base]21@last_interaction = nil22@connection_use_mutex = Mutex.new23super24end2526def register_interaction27@last_interaction = Process.clock_gettime(Process::CLOCK_MONOTONIC)28end2930# @return [Array<String>] LDAP servers naming contexts31def naming_contexts32@naming_contexts ||= search_root_dse[:namingcontexts]33end3435# @return [String] LDAP servers Base DN36def base_dn37@base_dn ||= discover_base_dn38end3940# @return [String, nil] LDAP servers Schema DN, nil if one isn't found41def schema_dn42@schema_dn ||= discover_schema_naming_context43end4445# @return [String] The remote IP address that LDAP is running on46def peerhost47host48end4950# @return [Integer] The remote port that LDAP is running on51def peerport52port53end5455# @return [String] The remote peer information containing IP and port56def peerinfo57"#{peerhost}:#{peerport}"58end5960def username61@auth[:username]62end6364def realm65@auth[:domain]66end6768def password69@auth[:password]70end7172def use_connection(args)73@connection_use_mutex.synchronize do74return super(args)75ensure76register_interaction77end78end7980# https://github.com/ruby-ldap/ruby-net-ldap/issues/1181# We want to keep the ldap connection open to use later82# but there's no built in way within the `Net::LDAP` library to do that83# so we're adding this function to do it instead84# @param connect_opts [Hash] Options for the LDAP connection.85def self._open(connect_opts)86client = new(connect_opts)87client._open88end8990# https://github.com/ruby-ldap/ruby-net-ldap/issues/1191def _open92raise Net::LDAP::AlreadyOpenedError, 'Open already in progress' if @open_connection9394instrument 'open.net_ldap' do |payload|95@open_connection = new_connection96@socket = @open_connection.socket97payload[:connection] = @open_connection98payload[:bind] = @result = @open_connection.bind(@auth)99register_interaction100return self101end102end103104def discover_schema_naming_context105result = search(base: '', attributes: [:schemanamingcontext], scope: Net::LDAP::SearchScope_BaseObject)106if result.first && !result.first[:schemanamingcontext].empty?107schema_dn = result.first[:schemanamingcontext].first108ilog("#{peerinfo} Discovered Schema DN: #{schema_dn}")109return schema_dn110end111wlog("#{peerinfo} Could not discover Schema DN")112nil113end114115def discover_base_dn116unless naming_contexts117elog("#{peerinfo} Base DN cannot be determined, no naming contexts available")118return119end120121# NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN.122result = naming_contexts.select { |context| context =~ /^([Dd][Cc]=[A-Za-z0-9-]+,?)+$/ }123.reject { |context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/ }124if result.blank?125elog("#{peerinfo} A base DN matching the expected format could not be found!")126return127end128base_dn = result[0]129130dlog("#{peerinfo} Discovered base DN: #{base_dn}")131base_dn132end133134# Monkeypatch upstream library to support the extended Whoami request. Delete135# this after https://github.com/ruby-ldap/ruby-net-ldap/pull/425 is released.136# This is not the only occurrence of a patch for this functionality.137def ldapwhoami(args = {})138instrument "ldapwhoami.net_ldap", args do |payload|139@result = use_connection(args, &:ldapwhoami)140if @result.success?141@result.extended_response142else143raise Net::LDAP::Error, "#{peerinfo} LDAP Error: #{@result.error_message}"144end145end146end147end148end149end150end151152153