Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/ldap/client.rb
52178 views
1
require 'net/ldap'
2
3
module Rex
4
module Proto
5
module LDAP
6
# This is a Rex Proto wrapper around the Net::LDAP client which is currently coming from the 'net-ldap' gem.
7
# The purpose of this wrapper is to provide 'peerhost' and 'peerport' methods to ensure the client interfaces
8
# are consistent between various session clients.
9
class Client < Net::LDAP
10
11
# @return [Rex::Socket]
12
attr_reader :socket
13
14
# [Time] The last time an interaction occurred on the connection (for keep-alive purposes)
15
attr_reader :last_interaction
16
17
# [Mutex] Control access to the connection. One at a time.
18
attr_reader :connection_use_mutex
19
20
def initialize(args)
21
@base_dn = args[:base]
22
@last_interaction = nil
23
@connection_use_mutex = Mutex.new
24
super
25
end
26
27
def register_interaction
28
@last_interaction = Process.clock_gettime(Process::CLOCK_MONOTONIC)
29
end
30
31
# @return [Array<String>] LDAP servers naming contexts
32
def naming_contexts
33
@naming_contexts ||= search_root_dse[:namingcontexts]
34
end
35
36
# @return [String] LDAP servers Base DN
37
def base_dn
38
@base_dn ||= discover_base_dn
39
end
40
41
# @return [String, nil] LDAP servers Schema DN, nil if one isn't found
42
def schema_dn
43
@schema_dn ||= discover_schema_naming_context
44
end
45
46
# @return [String] The remote IP address that LDAP is running on
47
def peerhost
48
host
49
end
50
51
# @return [Integer] The remote port that LDAP is running on
52
def peerport
53
port
54
end
55
56
# @return [String] The remote peer information containing IP and port
57
def peerinfo
58
"#{peerhost}:#{peerport}"
59
end
60
61
def username
62
@auth[:username]
63
end
64
65
def realm
66
@auth[:domain]
67
end
68
69
def password
70
@auth[:password]
71
end
72
73
def use_connection(args)
74
@connection_use_mutex.synchronize do
75
return super(args)
76
ensure
77
register_interaction
78
end
79
end
80
81
# https://github.com/ruby-ldap/ruby-net-ldap/issues/11
82
# We want to keep the ldap connection open to use later
83
# but there's no built in way within the `Net::LDAP` library to do that
84
# so we're adding this function to do it instead
85
# @param connect_opts [Hash] Options for the LDAP connection.
86
def self._open(connect_opts)
87
client = new(connect_opts)
88
client._open
89
end
90
91
# https://github.com/ruby-ldap/ruby-net-ldap/issues/11
92
def _open
93
raise Net::LDAP::AlreadyOpenedError, 'Open already in progress' if @open_connection
94
95
instrument 'open.net_ldap' do |payload|
96
@open_connection = new_connection
97
@socket = @open_connection.socket
98
payload[:connection] = @open_connection
99
payload[:bind] = @result = @open_connection.bind(@auth)
100
register_interaction
101
return self
102
end
103
end
104
105
def discover_schema_naming_context
106
result = search(base: '', attributes: [:schemanamingcontext], scope: Net::LDAP::SearchScope_BaseObject)
107
if result.first && !result.first[:schemanamingcontext].empty?
108
schema_dn = result.first[:schemanamingcontext].first
109
ilog("#{peerinfo} Discovered Schema DN: #{schema_dn}")
110
return schema_dn
111
end
112
wlog("#{peerinfo} Could not discover Schema DN")
113
nil
114
end
115
116
def discover_base_dn
117
unless naming_contexts
118
elog("#{peerinfo} Base DN cannot be determined, no naming contexts available")
119
return
120
end
121
122
# NOTE: Find the first entry that starts with `DC=` as this will likely be the base DN.
123
result = naming_contexts.select { |context| context =~ /^([Dd][Cc]=[A-Za-z0-9-]+,?)+$/ }
124
.reject { |context| context =~ /(Configuration)|(Schema)|(ForestDnsZones)/ }
125
if result.blank?
126
elog("#{peerinfo} A base DN matching the expected format could not be found!")
127
return
128
end
129
base_dn = result[0]
130
131
dlog("#{peerinfo} Discovered base DN: #{base_dn}")
132
base_dn
133
end
134
135
# Monkeypatch upstream library to support the extended Whoami request. Delete
136
# this after https://github.com/ruby-ldap/ruby-net-ldap/pull/425 is released.
137
# This is not the only occurrence of a patch for this functionality.
138
def ldapwhoami(args = {})
139
instrument "ldapwhoami.net_ldap", args do |payload|
140
@result = use_connection(args, &:ldapwhoami)
141
if @result.success?
142
@result.extended_response
143
else
144
raise Net::LDAP::Error, "#{peerinfo} LDAP Error: #{@result.error_message}"
145
end
146
end
147
end
148
end
149
end
150
end
151
end
152
153