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/kerberos/client.rb
Views: 11704
1
# -*- coding: binary -*-
2
3
require 'rex/stopwatch'
4
5
module Rex
6
module Proto
7
module Kerberos
8
# This class is a representation of a kerberos client.
9
class Client
10
# @!attribute host
11
# @return [String] The kerberos server host
12
attr_accessor :host
13
# @!attribute port
14
# @return [Integer] The kerberos server port
15
attr_accessor :port
16
# @!attribute proxies
17
# @return [String,nil] The proxy directive to use for the socket
18
attr_accessor :proxies
19
# @!attribute timeout
20
# @return [Integer] The connect / read timeout
21
attr_accessor :timeout
22
# @todo Support UDP
23
# @!attribute protocol
24
# @return [String] The transport protocol used (tcp/udp)
25
attr_accessor :protocol
26
# @!attribute connection
27
# @return [IO] The connection established through Rex sockets
28
attr_accessor :connection
29
# @!attribute context
30
# @return [Hash] The Msf context where the connection belongs to
31
attr_accessor :context
32
33
def initialize(opts = {})
34
self.host = opts[:host]
35
self.port = (opts[:port] || 88).to_i
36
self.proxies = opts[:proxies]
37
self.timeout = (opts[:timeout] || 10).to_i
38
self.protocol = opts[:protocol] || 'tcp'
39
self.context = opts[:context] || {}
40
end
41
42
# Creates a connection through a Rex socket
43
#
44
# @return [Rex::Socket::Tcp]
45
# @raise [RuntimeError] if the connection can not be created
46
def connect
47
return connection if connection
48
raise ArgumentError, 'Missing remote address' unless self.host && self.port
49
case protocol
50
when 'tcp'
51
self.connection = create_tcp_connection
52
when 'udp'
53
raise ::NotImplementedError, 'Kerberos Client: UDP not supported'
54
else
55
raise ::RuntimeError, 'Kerberos Client: unknown transport protocol'
56
end
57
58
connection
59
end
60
61
# Closes the connection
62
def close
63
if connection
64
connection.shutdown
65
connection.close unless connection.closed?
66
end
67
68
self.connection = nil
69
end
70
71
# Sends a kerberos request through the connection
72
#
73
# @param req [Rex::Proto::Kerberos::Model::KdcRequest] the request to send
74
# @return [Integer] the number of bytes sent
75
# @raise [RuntimeError] if the transport protocol is unknown
76
# @raise [NotImplementedError] if the transport protocol isn't supported
77
def send_request(req)
78
connect
79
80
sent = 0
81
case protocol
82
when 'tcp'
83
sent = send_request_tcp(req)
84
when 'udp'
85
sent = send_request_udp(req)
86
else
87
raise ::RuntimeError, 'Kerberos Client: unknown transport protocol'
88
end
89
90
sent
91
end
92
93
# Receives a kerberos response through the connection
94
#
95
# @return [<Rex::Proto::Kerberos::Model::KrbError, Rex::Proto::Kerberos::Model::KdcResponse>] the kerberos
96
# response message
97
# @raise [RuntimeError] if the connection isn't established, the transport protocol is unknown, not supported
98
# or the response can't be parsed
99
# @raise [NotImplementedError] if the transport protocol isn't supported
100
def recv_response
101
if connection.nil?
102
raise ::RuntimeError, 'Kerberos Client: connection not established'
103
end
104
105
res = nil
106
case protocol
107
when 'tcp'
108
res = recv_response_tcp
109
when 'udp'
110
res = recv_response_udp
111
else
112
raise ::RuntimeError, 'Kerberos Client: unknown transport protocol'
113
end
114
115
res
116
end
117
118
# Sends a kerberos request, and reads the response through the connection
119
#
120
# @param req [Rex::Proto::Kerberos::Model::KdcRequest] the request to send
121
# @return [<Rex::Proto::Kerberos::Model::KrbError, Rex::Proto::Kerberos::Model::KdcResponse>] The kerberos message
122
# @raise [RuntimeError] if the transport protocol is unknown or the response can't be parsed.
123
# @raise [NotImplementedError] if the transport protocol isn't supported
124
def send_recv(req)
125
send_request(req)
126
res = recv_response
127
128
res
129
end
130
131
private
132
133
# Creates a TCP connection using Rex::Socket::Tcp
134
#
135
# @return [Rex::Socket::Tcp]
136
def create_tcp_connection
137
self.connection = Rex::Socket::Tcp.create(
138
'PeerHost' => host,
139
'PeerPort' => port.to_i,
140
'Proxies' => proxies,
141
'Context' => context,
142
'Timeout' => timeout
143
)
144
end
145
146
# Sends a Kerberos Request over a tcp connection
147
#
148
# @param req [Rex::Proto::Kerberos::Model::KdcRequest] the request to send
149
# @return [Integer] the number of bytes sent
150
# @raise [RuntimeError] if the request can't be encoded
151
def send_request_tcp(req)
152
data = req.encode
153
length = [data.length].pack('N')
154
connection.put(length + data)
155
end
156
157
# UDP isn't supported
158
#
159
# @raise [NotImplementedError]
160
def send_request_udp(req)
161
raise ::NotImplementedError, 'Kerberos Client: UDP unsupported'
162
end
163
164
# Receives a Kerberos Response over a tcp connection
165
#
166
# @return [<Rex::Proto::Kerberos::Model::KrbError, Rex::Proto::Kerberos::Model::KdcResponse>] the kerberos message response
167
# @raise [Rex::Proto::Kerberos::Model::Error::KerberosDecodingError] if the response can't be processed
168
# @raise [EOFError] if expected data can't be read
169
def recv_response_tcp
170
remaining = timeout
171
length_raw, elapsed_time = Rex::Stopwatch.elapsed_time do
172
connection.get_once(4, remaining)
173
end
174
remaining -= elapsed_time
175
unless length_raw && length_raw.length == 4
176
if remaining <= 0
177
raise Rex::TimeoutError, 'Kerberos Client: failed to read response length due to timeout'
178
end
179
180
raise ::EOFError, 'Kerberos Client: failed to read response length'
181
end
182
length = length_raw.unpack('N')[0]
183
184
data = ''
185
while data.length < length && remaining > 0
186
chunk, elapsed_time = Rex::Stopwatch.elapsed_time do
187
connection.get_once(length - data.length, remaining)
188
end
189
190
remaining -= elapsed_time
191
break if chunk.nil?
192
193
data << chunk
194
end
195
196
unless data.length == length
197
if remaining <= 0
198
raise Rex::TimeoutError, 'Kerberos Client: failed to read response due to timeout'
199
end
200
201
raise ::EOFError, 'Kerberos Client: failed to read response'
202
end
203
204
decode_kerb_response(data)
205
end
206
207
# UDP isn't supported
208
#
209
# @raise [NotImplementedError]
210
def recv_response_udp
211
raise ::NotImplementedError, 'Kerberos Client: UDP unsupported'
212
end
213
214
private
215
216
# Decodes a Kerberos response
217
#
218
# @param data [String] the raw response message
219
# @return [<Rex::Proto::Kerberos::Model::KrbError, Rex::Proto::Kerberos::Model::KdcResponse, Rex::Proto::Kerberos::Model::KrbError>] the kerberos message response
220
# @raise [Rex::Proto::Kerberos::Model::Error::KerberosDecodingError] if the response can't be processed
221
def decode_kerb_response(data)
222
asn1 = OpenSSL::ASN1.decode(data)
223
msg_type = asn1.value[0].value[1].value[0].value
224
225
case msg_type
226
when Rex::Proto::Kerberos::Model::KRB_ERROR
227
res = Rex::Proto::Kerberos::Model::KrbError.decode(asn1)
228
when Rex::Proto::Kerberos::Model::AS_REP, Rex::Proto::Kerberos::Model::TGS_REP
229
res = Rex::Proto::Kerberos::Model::KdcResponse.decode(asn1)
230
when Rex::Proto::Kerberos::Model::AP_REP
231
res = Rex::Proto::Kerberos::Model::ApRep.decode(asn1)
232
else
233
raise ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError, 'Kerberos Client: Unknown response'
234
end
235
236
res
237
end
238
end
239
end
240
end
241
end
242
243