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
56915 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
lsock.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
145
class TcpServerChannel
146
include Rex::IO::StreamServer
147
148
attr_reader :params
149
150
def initialize(params, client)
151
@params = params
152
@client = client
153
@channels = []
154
@closed = false
155
@mutex = Mutex.new
156
@condition = ConditionVariable.new
157
158
if params.ssl
159
extend(Rex::Socket::SslTcpServer)
160
initsock(params)
161
end
162
end
163
164
def accept(opts = {})
165
timeout = opts['Timeout']
166
if (timeout.nil? || timeout <= 0)
167
timeout = nil
168
end
169
170
@mutex.synchronize {
171
if @channels.length > 0
172
return _accept
173
end
174
@condition.wait(@mutex, timeout)
175
return _accept
176
}
177
end
178
179
def closed?
180
@closed
181
end
182
183
def close
184
if !closed?
185
@closed = @client.stop_server_channel(@params.localhost, @params.localport)
186
end
187
end
188
189
def create(cid, ssh_channel, peer_host, peer_port)
190
@mutex.synchronize {
191
peer_info = {
192
'PeerHost' => peer_host,
193
'PeerPort' => peer_port
194
}
195
params = @params.merge(peer_info)
196
channel = TcpClientChannel.new(@client, cid, ssh_channel, params)
197
@channels.insert(0, channel)
198
199
# Let any waiting thread know we're ready
200
@condition.signal
201
}
202
end
203
204
attr_reader :client
205
206
protected
207
208
def _accept
209
result = nil
210
channel = @channels.pop
211
if channel
212
result = channel.lsock
213
end
214
result
215
end
216
217
end
218
219
#
220
# Create a sessions instance from an SshConnection. This will handle creating
221
# a new command stream.
222
#
223
# @param ssh_connection [Net::SSH::Connection] The SSH connection to create a
224
# session instance for.
225
# @param opts [Hash] Optional parameters to pass to the session object.
226
def initialize(ssh_connection, opts = {})
227
@ssh_connection = ssh_connection
228
@sock = ssh_connection.transport.socket
229
@server_channels = {}
230
231
initialize_channels
232
@channel_ticker = 0
233
234
# Be alerted to reverse port forward connections (once we start listening on a port)
235
ssh_connection.on_open_channel('forwarded-tcpip', &method(:on_got_remote_connection))
236
super(nil, opts)
237
end
238
239
def bootstrap(datastore = {}, handler = nil)
240
# this won't work after the rstream is initialized, so do it first
241
@platform = Metasploit::Framework::Ssh::Platform.get_platform(ssh_connection)
242
if @platform == 'windows'
243
extend(Msf::Sessions::WindowsEscaping)
244
elsif Metasploit::Framework::Ssh::Platform.is_posix(@platform)
245
extend(Msf::Sessions::UnixEscaping)
246
else
247
raise ::Net::SSH::Exception.new("Unknown platform: #{platform}")
248
end
249
250
# if the platform is known, it was recovered by communicating with the device, so skip verification, also not all
251
# shells accessed through SSH may respond to the echo command issued for verification as expected
252
datastore['AutoVerifySession'] &= @platform.blank?
253
254
@rstream = Net::SSH::CommandStream.new(ssh_connection, session: self, logger: self).lsock
255
super
256
257
@info = "SSH #{username} @ #{@peer_info}"
258
end
259
260
def desc
261
"SSH"
262
end
263
264
#
265
# Create a network socket using this session. At this time, only TCP client
266
# connections can be made (like SSH port forwarding) while TCP server sockets
267
# can not be opened (SSH reverse port forwarding). The SSH specification does
268
# not define a UDP channel, so that is not supported either.
269
#
270
# @param params [Rex::Socket::Parameters] The parameters that should be used
271
# to open the socket.
272
#
273
# @raise [Rex::ConnectionError] If the connection fails, timesout or is not
274
# supported, a ConnectionError will be raised.
275
# @return [TcpClientChannel] The connected TCP client channel.
276
def create(params)
277
# Notify handlers before we create the socket
278
notify_before_socket_create(self, params)
279
280
if params.proto == 'tcp'
281
if params.server
282
sock = create_server_channel(params)
283
else
284
sock = create_client_channel(params)
285
end
286
elsif params.proto == 'udp'
287
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: 'UDP sockets are not supported by SSH sessions.')
288
end
289
290
raise ::Rex::ConnectionError unless sock
291
292
# Notify now that we've created the socket
293
notify_socket_created(self, sock, params)
294
295
sock
296
end
297
298
def supports_udp?
299
false
300
end
301
302
def create_server_channel(params)
303
msf_channel = nil
304
mutex = Mutex.new
305
condition = ConditionVariable.new
306
timed_out = false
307
@ssh_connection.send_global_request('tcpip-forward', :string, params.localhost, :long, params.localport) do |success, response|
308
mutex.synchronize {
309
if params.localport == 0
310
params.localport = response.read_long
311
end
312
313
if success
314
if timed_out
315
# We're not using the port; clean it up
316
elog("Remote forwarding on #{Rex::Socket.to_authority(params.localhost, params.localport)} succeeded after timeout, stopping channel to clean up dangling port")
317
stop_server_channel(params.localhost, params.localport)
318
else
319
dlog("Remote forwarding from #{params.localhost} established on port #{params.localport}")
320
key = [params.localhost, params.localport]
321
msf_channel = TcpServerChannel.new(params, self)
322
@server_channels[key] = msf_channel
323
end
324
else
325
elog("Remote forwarding failed on #{Rex::Socket.to_authority(params.localhost, params.localport)}")
326
end
327
condition.signal
328
}
329
end
330
331
mutex.synchronize {
332
condition.wait(mutex, params.timeout)
333
unless msf_channel
334
timed_out = true
335
end
336
}
337
338
# Return the server channel itself
339
msf_channel
340
end
341
342
def stop_server_channel(host, port)
343
completed_event = Rex::Sync::Event.new
344
dlog("Cancelling tcpip-forward to #{host}:#{port}")
345
@ssh_connection.send_global_request('cancel-tcpip-forward', :string, host, :long, port) do |success, _response|
346
if success
347
key = [host, port]
348
@server_channels.delete(key)
349
ilog("Reverse SSH listener on #{host}:#{port} stopped")
350
else
351
elog("Could not stop reverse listener on #{host}:#{port}")
352
end
353
completed_event.set
354
end
355
timeout = 5 # seconds
356
begin
357
completed_event.wait(timeout)
358
true
359
rescue ::Timeout::Error
360
false
361
end
362
end
363
364
def create_client_channel(params)
365
msf_channel = nil
366
mutex = Mutex.new
367
condition = ConditionVariable.new
368
opened = false
369
ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |_|
370
dlog("new direct-tcpip channel opened to #{Rex::Socket.to_authority(params.peerhost, params.peerport)}")
371
opened = true
372
mutex.synchronize do
373
condition.signal
374
end
375
end
376
failure_reason_code = nil
377
ssh_channel.on_open_failed do |_ch, code, desc|
378
failure_reason_code = code
379
wlog("failed to open SSH channel (code: #{code.inspect}, description: #{desc.inspect})")
380
mutex.synchronize do
381
condition.signal
382
end
383
end
384
385
mutex.synchronize do
386
timeout = params.timeout.to_i <= 0 ? nil : params.timeout
387
condition.wait(mutex, timeout)
388
end
389
390
unless opened
391
ssh_channel.close
392
393
raise ::Rex::ConnectionTimeout.new(params.peerhost, params.peerport) if failure_reason_code.nil?
394
395
case failure_reason_code
396
when ChannelFailureReason::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED
397
reason = 'The SSH channel request was administratively prohibited.'
398
when ChannelFailureReason::SSH_OPEN_UNKNOWN_CHANNEL_TYPE
399
reason = 'The SSH channel type is not supported.'
400
when ChannelFailureReason::SSH_OPEN_RESOURCE_SHORTAGE
401
reason = 'The SSH channel request was denied because of a resource shortage.'
402
end
403
404
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: reason)
405
end
406
msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, ssh_channel, params)
407
sock = msf_channel.lsock
408
409
# Notify now that we've created the socket
410
notify_socket_created(self, sock, params)
411
412
sock
413
end
414
415
# The SSH server has told us that there's a port forwarding request.
416
# Find the relevant server channel and inform it.
417
def on_got_remote_connection(_session, channel, packet)
418
connected_address = packet.read_string
419
connected_port = packet.read_long
420
originator_address = packet.read_string
421
originator_port = packet.read_long
422
ilog("Received connection: #{connected_address}:#{connected_port} <--> #{originator_address}:#{originator_port}")
423
# Find the correct TcpServerChannel
424
#
425
key = [connected_address, connected_port]
426
server_channel = @server_channels[key]
427
server_channel.create(@channel_ticker += 1, channel, originator_address, originator_port)
428
end
429
430
def cleanup
431
channels.each_value(&:close)
432
@server_channels.each_value(&:close)
433
434
super
435
end
436
437
attr_reader :sock, :ssh_connection
438
end
439
end
440
441