Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/base/sessions/ssh_command_shell_bind.rb
19500 views
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
if @platform == 'windows'
242
extend(Msf::Sessions::WindowsEscaping)
243
elsif Metasploit::Framework::Ssh::Platform.is_posix(@platform)
244
extend(Msf::Sessions::UnixEscaping)
245
else
246
raise ::Net::SSH::Exception.new("Unknown platform: #{platform}")
247
end
248
249
# if the platform is known, it was recovered by communicating with the device, so skip verification, also not all
250
# shells accessed through SSH may respond to the echo command issued for verification as expected
251
datastore['AutoVerifySession'] &= @platform.blank?
252
253
@rstream = Net::SSH::CommandStream.new(ssh_connection, session: self, logger: self).lsock
254
super
255
256
@info = "SSH #{username} @ #{@peer_info}"
257
end
258
259
def desc
260
"SSH"
261
end
262
263
#
264
# Create a network socket using this session. At this time, only TCP client
265
# connections can be made (like SSH port forwarding) while TCP server sockets
266
# can not be opened (SSH reverse port forwarding). The SSH specification does
267
# not define a UDP channel, so that is not supported either.
268
#
269
# @param params [Rex::Socket::Parameters] The parameters that should be used
270
# to open the socket.
271
#
272
# @raise [Rex::ConnectionError] If the connection fails, timesout or is not
273
# supported, a ConnectionError will be raised.
274
# @return [TcpClientChannel] The connected TCP client channel.
275
def create(params)
276
# Notify handlers before we create the socket
277
notify_before_socket_create(self, params)
278
279
if params.proto == 'tcp'
280
if params.server
281
sock = create_server_channel(params)
282
else
283
sock = create_client_channel(params)
284
end
285
elsif params.proto == 'udp'
286
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: 'UDP sockets are not supported by SSH sessions.')
287
end
288
289
raise ::Rex::ConnectionError unless sock
290
291
# Notify now that we've created the socket
292
notify_socket_created(self, sock, params)
293
294
sock
295
end
296
297
def supports_udp?
298
false
299
end
300
301
def create_server_channel(params)
302
msf_channel = nil
303
mutex = Mutex.new
304
condition = ConditionVariable.new
305
timed_out = false
306
@ssh_connection.send_global_request('tcpip-forward', :string, params.localhost, :long, params.localport) do |success, response|
307
mutex.synchronize {
308
remote_port = params.localport
309
remote_port = response.read_long if remote_port == 0
310
if success
311
if timed_out
312
# We're not using the port; clean it up
313
elog("Remote forwarding on #{params.localhost}:#{params.localport} succeeded after timeout. Stopping channel to clean up dangling port")
314
stop_server_channel(params.localhost, remote_port)
315
else
316
dlog("Remote forwarding from #{params.localhost} established on port #{remote_port}")
317
key = [params.localhost, remote_port]
318
msf_channel = TcpServerChannel.new(params, self, params.localhost, remote_port)
319
@server_channels[key] = msf_channel
320
end
321
else
322
elog("Remote forwarding failed on #{params.localhost}:#{params.localport}")
323
end
324
condition.signal
325
}
326
end
327
328
mutex.synchronize {
329
condition.wait(mutex, params.timeout)
330
unless msf_channel
331
timed_out = true
332
end
333
}
334
335
# Return the server channel itself
336
msf_channel
337
end
338
339
def stop_server_channel(host, port)
340
completed_event = Rex::Sync::Event.new
341
dlog("Cancelling tcpip-forward to #{host}:#{port}")
342
@ssh_connection.send_global_request('cancel-tcpip-forward', :string, host, :long, port) do |success, _response|
343
if success
344
key = [host, port]
345
@server_channels.delete(key)
346
ilog("Reverse SSH listener on #{host}:#{port} stopped")
347
else
348
elog("Could not stop reverse listener on #{host}:#{port}")
349
end
350
completed_event.set
351
end
352
timeout = 5 # seconds
353
begin
354
completed_event.wait(timeout)
355
true
356
rescue ::Timeout::Error
357
false
358
end
359
end
360
361
def create_client_channel(params)
362
msf_channel = nil
363
mutex = Mutex.new
364
condition = ConditionVariable.new
365
opened = false
366
ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |_|
367
dlog("new direct-tcpip channel opened to #{Rex::Socket.is_ipv6?(params.peerhost) ? '[' + params.peerhost + ']' : params.peerhost}:#{params.peerport}")
368
opened = true
369
mutex.synchronize do
370
condition.signal
371
end
372
end
373
failure_reason_code = nil
374
ssh_channel.on_open_failed do |_ch, code, desc|
375
failure_reason_code = code
376
wlog("failed to open SSH channel (code: #{code.inspect}, description: #{desc.inspect})")
377
mutex.synchronize do
378
condition.signal
379
end
380
end
381
382
mutex.synchronize do
383
timeout = params.timeout.to_i <= 0 ? nil : params.timeout
384
condition.wait(mutex, timeout)
385
end
386
387
unless opened
388
ssh_channel.close
389
390
raise ::Rex::ConnectionTimeout.new(params.peerhost, params.peerport) if failure_reason_code.nil?
391
392
case failure_reason_code
393
when ChannelFailureReason::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED
394
reason = 'The SSH channel request was administratively prohibited.'
395
when ChannelFailureReason::SSH_OPEN_UNKNOWN_CHANNEL_TYPE
396
reason = 'The SSH channel type is not supported.'
397
when ChannelFailureReason::SSH_OPEN_RESOURCE_SHORTAGE
398
reason = 'The SSH channel request was denied because of a resource shortage.'
399
end
400
401
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: reason)
402
end
403
msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, ssh_channel, params)
404
sock = msf_channel.lsock
405
406
# Notify now that we've created the socket
407
notify_socket_created(self, sock, params)
408
409
sock
410
end
411
412
# The SSH server has told us that there's a port forwarding request.
413
# Find the relevant server channel and inform it.
414
def on_got_remote_connection(_session, channel, packet)
415
connected_address = packet.read_string
416
connected_port = packet.read_long
417
originator_address = packet.read_string
418
originator_port = packet.read_long
419
ilog("Received connection: #{connected_address}:#{connected_port} <--> #{originator_address}:#{originator_port}")
420
# Find the correct TcpServerChannel
421
#
422
key = [connected_address, connected_port]
423
server_channel = @server_channels[key]
424
server_channel.create(@channel_ticker += 1, channel, originator_address, originator_port)
425
end
426
427
def cleanup
428
channels.each_value(&:close)
429
@server_channels.each_value(&:close)
430
431
super
432
end
433
434
attr_reader :sock, :ssh_connection
435
end
436
end
437
438