CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/base/sessions/ssh_command_shell_bind.rb
Views: 1904
1
# -*- coding: binary -*-
2
3
require 'metasploit/framework/ssh/platform'
4
require 'rex/post/channel'
5
require 'rex/post/meterpreter/channels/socket_abstraction'
6
7
module Msf::Sessions
8
#
9
# This class provides a session for SSH client connections, where Metasploit
10
# has authenticated to a remote SSH server. It is compatible with the
11
# Net::SSH library.
12
#
13
class SshCommandShellBind < Msf::Sessions::CommandShell
14
15
include Msf::Session::Comm
16
include Rex::Post::Channel::Container
17
18
# see: https://datatracker.ietf.org/doc/html/rfc4254#section-5.1
19
module ChannelFailureReason
20
SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1
21
SSH_OPEN_CONNECT_FAILED = 2
22
SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3
23
SSH_OPEN_RESOURCE_SHORTAGE = 4
24
end
25
26
#
27
# This is a Metasploit Framework channel object that wraps a Net::SSH native
28
# channel object.
29
#
30
class TcpClientChannel
31
include Rex::Post::Channel::StreamAbstraction
32
33
#
34
# This is a common interface that socket paris are extended with to be
35
# compatible with pivoting.
36
#
37
module SocketInterface
38
include Rex::Post::Channel::SocketAbstraction::SocketInterface
39
40
def type?
41
'tcp'
42
end
43
end
44
45
#
46
# Create a new TcpClientChannel instance.
47
#
48
# @param client [SshCommandShellBind] The command shell session that this
49
# channel instance belongs to.
50
# @param cid [Integer] The channel ID.
51
# @param ssh_channel [Net::SSH::Connection::Channel] The connected SSH
52
# channel.
53
# @param params [Rex::Socket::Parameters] The parameters that were used to
54
# open the channel.
55
def initialize(client, cid, ssh_channel, params)
56
initialize_abstraction
57
58
@client = client
59
@cid = cid
60
@ssh_channel = ssh_channel
61
@params = params
62
@mutex = Mutex.new
63
64
ssh_channel.on_close do |_ch|
65
dlog('ssh_channel#on_close closing the sock')
66
close
67
end
68
69
ssh_channel.on_data do |_ch, data|
70
# dlog("ssh_channel#on_data received #{data.length} bytes")
71
rsock.syswrite(data)
72
end
73
74
ssh_channel.on_eof do |_ch|
75
dlog('ssh_channel#on_eof shutting down the socket')
76
rsock.shutdown(Socket::SHUT_WR)
77
end
78
79
lsock.extend(SocketInterface)
80
lsock.channel = self
81
82
rsock.extend(SocketInterface)
83
rsock.channel = self
84
85
lsock.extend(Rex::Socket::SslTcp) if params.ssl && !params.server
86
87
# synchronize access so the socket isn't closed while initializing, this is particularly important for SSL
88
lsock.synchronize_access { lsock.initsock(params) }
89
rsock.synchronize_access { rsock.initsock(params) }
90
91
client.add_channel(self)
92
end
93
94
def closed?
95
@cid.nil?
96
end
97
98
def close
99
cid = @cid
100
@mutex.synchronize do
101
return if closed?
102
103
@cid = nil
104
end
105
106
@client.remove_channel(cid)
107
cleanup_abstraction
108
@ssh_channel.close
109
end
110
111
def close_write
112
if closed?
113
raise IOError, 'Channel has been closed.', caller
114
end
115
116
@ssh_channel.eof!
117
end
118
119
#
120
# Write *buf* to the channel, optionally truncating it to *length* bytes.
121
#
122
# @param [String] buf The data to write to the channel.
123
# @param [Integer] length An optional length to truncate *data* to before
124
# sending it.
125
def write(buf, length = nil)
126
if closed?
127
raise IOError, 'Channel has been closed.', caller
128
end
129
130
if !length.nil? && buf.length >= length
131
buf = buf[0..length]
132
end
133
134
@ssh_channel.send_data(buf)
135
buf.length
136
end
137
138
attr_reader :cid, :client, :params
139
end
140
141
# Represents an SSH reverse port forward.
142
# Will receive connection messages back from the SSH server,
143
# whereupon a TcpClientChannel will be opened
144
class TcpServerChannel
145
include Rex::IO::StreamServer
146
147
def initialize(params, client, host, port)
148
@params = params
149
@client = client
150
@host = host
151
@port = port
152
@channels = []
153
@closed = false
154
@mutex = Mutex.new
155
@condition = ConditionVariable.new
156
157
if params.ssl
158
extend(Rex::Socket::SslTcpServer)
159
initsock(params)
160
end
161
end
162
163
def accept(opts = {})
164
timeout = opts['Timeout']
165
if (timeout.nil? || timeout <= 0)
166
timeout = nil
167
end
168
169
@mutex.synchronize {
170
if @channels.length > 0
171
return _accept
172
end
173
@condition.wait(@mutex, timeout)
174
return _accept
175
}
176
end
177
178
def closed?
179
@closed
180
end
181
182
def close
183
if !closed?
184
@closed = @client.stop_server_channel(@host, @port)
185
end
186
end
187
188
def create(cid, ssh_channel, peer_host, peer_port)
189
@mutex.synchronize {
190
peer_info = {
191
'PeerHost' => peer_host,
192
'PeerPort' => peer_port
193
}
194
params = @params.merge(peer_info)
195
channel = TcpClientChannel.new(@client, cid, ssh_channel, params)
196
@channels.insert(0, channel)
197
198
# Let any waiting thread know we're ready
199
@condition.signal
200
}
201
end
202
203
attr_reader :client
204
205
protected
206
207
def _accept
208
result = nil
209
channel = @channels.pop
210
if channel
211
result = channel.lsock
212
end
213
result
214
end
215
216
end
217
218
#
219
# Create a sessions instance from an SshConnection. This will handle creating
220
# a new command stream.
221
#
222
# @param ssh_connection [Net::SSH::Connection] The SSH connection to create a
223
# session instance for.
224
# @param opts [Hash] Optional parameters to pass to the session object.
225
def initialize(ssh_connection, opts = {})
226
@ssh_connection = ssh_connection
227
@sock = ssh_connection.transport.socket
228
@server_channels = {}
229
230
initialize_channels
231
@channel_ticker = 0
232
233
# Be alerted to reverse port forward connections (once we start listening on a port)
234
ssh_connection.on_open_channel('forwarded-tcpip', &method(:on_got_remote_connection))
235
super(nil, opts)
236
end
237
238
def bootstrap(datastore = {}, handler = nil)
239
# this won't work after the rstream is initialized, so do it first
240
@platform = Metasploit::Framework::Ssh::Platform.get_platform(ssh_connection)
241
242
# if the platform is known, it was recovered by communicating with the device, so skip verification, also not all
243
# shells accessed through SSH may respond to the echo command issued for verification as expected
244
datastore['AutoVerifySession'] &= @platform.blank?
245
246
@rstream = Net::SSH::CommandStream.new(ssh_connection).lsock
247
super
248
249
@info = "SSH #{username} @ #{@peer_info}"
250
end
251
252
def desc
253
"SSH"
254
end
255
256
#
257
# Create a network socket using this session. At this time, only TCP client
258
# connections can be made (like SSH port forwarding) while TCP server sockets
259
# can not be opened (SSH reverse port forwarding). The SSH specification does
260
# not define a UDP channel, so that is not supported either.
261
#
262
# @param params [Rex::Socket::Parameters] The parameters that should be used
263
# to open the socket.
264
#
265
# @raise [Rex::ConnectionError] If the connection fails, timesout or is not
266
# supported, a ConnectionError will be raised.
267
# @return [TcpClientChannel] The connected TCP client channel.
268
def create(params)
269
# Notify handlers before we create the socket
270
notify_before_socket_create(self, params)
271
272
if params.proto == 'tcp'
273
if params.server
274
sock = create_server_channel(params)
275
else
276
sock = create_client_channel(params)
277
end
278
elsif params.proto == 'udp'
279
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: 'UDP sockets are not supported by SSH sessions.')
280
end
281
282
raise ::Rex::ConnectionError unless sock
283
284
# Notify now that we've created the socket
285
notify_socket_created(self, sock, params)
286
287
sock
288
end
289
290
def supports_udp?
291
false
292
end
293
294
def create_server_channel(params)
295
msf_channel = nil
296
mutex = Mutex.new
297
condition = ConditionVariable.new
298
timed_out = false
299
@ssh_connection.send_global_request('tcpip-forward', :string, params.localhost, :long, params.localport) do |success, response|
300
mutex.synchronize {
301
remote_port = params.localport
302
remote_port = response.read_long if remote_port == 0
303
if success
304
if timed_out
305
# We're not using the port; clean it up
306
elog("Remote forwarding on #{params.localhost}:#{params.localport} succeeded after timeout. Stopping channel to clean up dangling port")
307
stop_server_channel(params.localhost, remote_port)
308
else
309
dlog("Remote forwarding from #{params.localhost} established on port #{remote_port}")
310
key = [params.localhost, remote_port]
311
msf_channel = TcpServerChannel.new(params, self, params.localhost, remote_port)
312
@server_channels[key] = msf_channel
313
end
314
else
315
elog("Remote forwarding failed on #{params.localhost}:#{params.localport}")
316
end
317
condition.signal
318
}
319
end
320
321
mutex.synchronize {
322
condition.wait(mutex, params.timeout)
323
unless msf_channel
324
timed_out = true
325
end
326
}
327
328
# Return the server channel itself
329
msf_channel
330
end
331
332
def stop_server_channel(host, port)
333
completed_event = Rex::Sync::Event.new
334
dlog("Cancelling tcpip-forward to #{host}:#{port}")
335
@ssh_connection.send_global_request('cancel-tcpip-forward', :string, host, :long, port) do |success, _response|
336
if success
337
key = [host, port]
338
@server_channels.delete(key)
339
ilog("Reverse SSH listener on #{host}:#{port} stopped")
340
else
341
elog("Could not stop reverse listener on #{host}:#{port}")
342
end
343
completed_event.set
344
end
345
timeout = 5 # seconds
346
begin
347
completed_event.wait(timeout)
348
true
349
rescue ::Timeout::Error
350
false
351
end
352
end
353
354
def create_client_channel(params)
355
msf_channel = nil
356
mutex = Mutex.new
357
condition = ConditionVariable.new
358
opened = false
359
ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |_|
360
dlog("new direct-tcpip channel opened to #{Rex::Socket.is_ipv6?(params.peerhost) ? '[' + params.peerhost + ']' : params.peerhost}:#{params.peerport}")
361
opened = true
362
mutex.synchronize do
363
condition.signal
364
end
365
end
366
failure_reason_code = nil
367
ssh_channel.on_open_failed do |_ch, code, desc|
368
failure_reason_code = code
369
wlog("failed to open SSH channel (code: #{code.inspect}, description: #{desc.inspect})")
370
mutex.synchronize do
371
condition.signal
372
end
373
end
374
375
mutex.synchronize do
376
timeout = params.timeout.to_i <= 0 ? nil : params.timeout
377
condition.wait(mutex, timeout)
378
end
379
380
unless opened
381
ssh_channel.close
382
383
raise ::Rex::ConnectionTimeout.new(params.peerhost, params.peerport) if failure_reason_code.nil?
384
385
case failure_reason_code
386
when ChannelFailureReason::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED
387
reason = 'The SSH channel request was administratively prohibited.'
388
when ChannelFailureReason::SSH_OPEN_UNKNOWN_CHANNEL_TYPE
389
reason = 'The SSH channel type is not supported.'
390
when ChannelFailureReason::SSH_OPEN_RESOURCE_SHORTAGE
391
reason = 'The SSH channel request was denied because of a resource shortage.'
392
end
393
394
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: reason)
395
end
396
msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, ssh_channel, params)
397
sock = msf_channel.lsock
398
399
# Notify now that we've created the socket
400
notify_socket_created(self, sock, params)
401
402
sock
403
end
404
405
# The SSH server has told us that there's a port forwarding request.
406
# Find the relevant server channel and inform it.
407
def on_got_remote_connection(_session, channel, packet)
408
connected_address = packet.read_string
409
connected_port = packet.read_long
410
originator_address = packet.read_string
411
originator_port = packet.read_long
412
ilog("Received connection: #{connected_address}:#{connected_port} <--> #{originator_address}:#{originator_port}")
413
# Find the correct TcpServerChannel
414
#
415
key = [connected_address, connected_port]
416
server_channel = @server_channels[key]
417
server_channel.create(@channel_ticker += 1, channel, originator_address, originator_port)
418
end
419
420
def cleanup
421
channels.each_value(&:close)
422
@server_channels.each_value(&:close)
423
424
super
425
end
426
427
attr_reader :sock, :ssh_connection
428
end
429
end
430
431