Class: Msf::Sessions::SshCommandShellBind

Inherits:
CommandShell show all
Includes:
Msf::Session::Comm, Rex::Post::Channel::Container
Defined in:
lib/msf/base/sessions/ssh_command_shell_bind.rb

Overview

This class provides a session for SSH client connections, where Metasploit has authenticated to a remote SSH server. It is compatible with the Net::SSH library.

Defined Under Namespace

Modules: ChannelFailureReason Classes: TcpClientChannel, TcpServerChannel

Instance Attribute Summary collapse

Attributes included from Rex::Post::Channel::Container

#channels

Attributes inherited from CommandShell

#arch, #banner, #max_threads, #platform

Attributes included from Msf::Session::Interactive

#rstream

Attributes included from Rex::Ui::Interactive

#completed, #interacting, #next_session, #on_command_proc, #on_print_proc, #orig_suspend, #orig_usr1, #orig_winch

Attributes included from Rex::Ui::Subscriber::Input

#user_input

Attributes included from Rex::Ui::Subscriber::Output

#user_output

Attributes included from Msf::Session

#alive, #db_record, #exploit, #exploit_datastore, #exploit_task, #exploit_uuid, #framework, #info, #machine_id, #payload_uuid, #routes, #sid, #sname, #target_host, #target_port, #username, #uuid, #via, #workspace

Attributes included from Framework::Offspring

#framework

Instance Method Summary collapse

Methods included from Rex::Post::Channel::Container

#add_channel, #find_channel, #initialize_channels, #remove_channel

Methods inherited from CommandShell

#_file_transfer, #_interact, #_interact_stream, #abort_foreground_supported, #binary_exists, binary_exists, can_cleanup_files, #cmd_background, #cmd_background_help, #cmd_download, #cmd_download_help, #cmd_help, #cmd_help_help, #cmd_irb, #cmd_irb_help, #cmd_pry, #cmd_pry_help, #cmd_resource, #cmd_resource_help, #cmd_sessions, #cmd_sessions_help, #cmd_shell, #cmd_shell_help, #cmd_source, #cmd_source_help, #cmd_upload, #cmd_upload_help, #commands, #docs_dir, #execute_file, #process_autoruns, #run_builtin_cmd, #run_single, #shell_close, #shell_command, #shell_init, #shell_read, #shell_write, #type, type

Methods included from Rex::Ui::Text::Resource

#load_resource

Methods included from Scriptable

#execute_file, #execute_script, included, #legacy_script_to_post_module

Methods included from Msf::Session::Provider::SingleCommandShell

#command_termination, #set_is_echo_shell, #shell_close, #shell_command_token, #shell_command_token_base, #shell_command_token_unix, #shell_command_token_win32, #shell_init, #shell_read, #shell_read_until_token, #shell_write

Methods included from Msf::Session::Basic

#_interact, #type

Methods included from Msf::Session::Interactive

#_interact, #_interact_complete, #_interrupt, #_suspend, #_usr1, #abort_foreground, #abort_foreground_supported, #comm_channel, #interactive?, #kill, #run_cmd, #tunnel_local, #tunnel_peer, #user_want_abort?

Methods included from Rex::Ui::Interactive

#_interact, #_interact_complete, #_interrupt, #_local_fd, #_remote_fd, #_stream_read_local_write_remote, #_stream_read_remote_write_local, #_suspend, #_winch, #detach, #handle_suspend, #handle_usr1, #handle_winch, #interact, #interact_stream, #prompt, #prompt_yesno, #restore_suspend, #restore_usr1, #restore_winch

Methods included from Rex::Ui::Subscriber

#copy_ui, #init_ui, #reset_ui

Methods included from Rex::Ui::Subscriber::Input

#gets

Methods included from Rex::Ui::Subscriber::Output

#flush, #print, #print_blank_line, #print_error, #print_good, #print_line, #print_status, #print_warning

Methods included from Msf::Session

#alive?, #comm_channel, #dead?, #inspect, #interactive?, #kill, #log_file_name, #log_source, #name, #name=, #register?, #session_host, #session_host=, #session_port, #session_port=, #session_type, #set_from_exploit, #set_via, #tunnel_local, #tunnel_peer, #tunnel_to_s, type, #type, #via_exploit, #via_payload

Constructor Details

#initialize(ssh_connection, opts = {}) ⇒ SshCommandShellBind

Create a sessions instance from an SshConnection. This will handle creating a new command stream.

Parameters:

  • ssh_connection (Net::SSH::Connection)

    The SSH connection to create a session instance for.

  • opts (Hash) (defaults to: {})

    Optional parameters to pass to the session object.



225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 225

def initialize(ssh_connection, opts = {})
  @ssh_connection = ssh_connection
  @sock = ssh_connection.transport.socket
  @server_channels = {}

  initialize_channels
  @channel_ticker = 0

  # Be alerted to reverse port forward connections (once we start listening on a port)
  ssh_connection.on_open_channel('forwarded-tcpip', &method(:on_got_remote_connection))
  super(nil, opts)
end

Instance Attribute Details

#sockObject (readonly)

Returns the value of attribute sock.



423
424
425
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 423

def sock
  @sock
end

#ssh_connectionObject (readonly)

Returns the value of attribute ssh_connection.



423
424
425
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 423

def ssh_connection
  @ssh_connection
end

Instance Method Details

#bootstrap(datastore = {}, handler = nil) ⇒ Object



238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 238

def bootstrap(datastore = {}, handler = nil)
  # this won't work after the rstream is initialized, so do it first
  @platform = Metasploit::Framework::Ssh::Platform.get_platform(ssh_connection)

  # if the platform is known, it was recovered by communicating with the device, so skip verification, also not all
  # shells accessed through SSH may respond to the echo command issued for verification as expected
  datastore['AutoVerifySession'] &= @platform.blank?

  @rstream = Net::SSH::CommandStream.new(ssh_connection).lsock
  super

  @info = "SSH #{username} @ #{@peer_info}"
end

#cleanupObject



416
417
418
419
420
421
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 416

def cleanup
  channels.each_value(&:close)
  @server_channels.each_value(&:close)

  super
end

#create(params) ⇒ TcpClientChannel

Create a network socket using this session. At this time, only TCP client connections can be made (like SSH port forwarding) while TCP server sockets can not be opened (SSH reverse port forwarding). The SSH specification does not define a UDP channel, so that is not supported either.

Parameters:

  • params (Rex::Socket::Parameters)

    The parameters that should be used to open the socket.

Returns:

Raises:

  • (Rex::ConnectionError)

    If the connection fails, timesout or is not supported, a ConnectionError will be raised.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 268

def create(params)
  # Notify handlers before we create the socket
  notify_before_socket_create(self, params)

  if params.proto == 'tcp'
    if params.server
      sock = create_server_channel(params)
    else
      sock = create_client_channel(params)
    end
  elsif params.proto == 'udp'
    raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: 'UDP sockets are not supported by SSH sessions.')
  end

  raise ::Rex::ConnectionError unless sock

  # Notify now that we've created the socket
  notify_socket_created(self, sock, params)

  sock
end

#create_client_channel(params) ⇒ Object



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 350

def create_client_channel(params)
  msf_channel = nil
  mutex = Mutex.new
  condition = ConditionVariable.new
  opened = false
  ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |_|
    dlog("new direct-tcpip channel opened to #{Rex::Socket.is_ipv6?(params.peerhost) ? '[' + params.peerhost + ']' : params.peerhost}:#{params.peerport}")
    opened = true
    mutex.synchronize do
      condition.signal
    end
  end
  failure_reason_code = nil
  ssh_channel.on_open_failed do |_ch, code, desc|
    failure_reason_code = code
    wlog("failed to open SSH channel (code: #{code.inspect}, description: #{desc.inspect})")
    mutex.synchronize do
      condition.signal
    end
  end

  mutex.synchronize do
    timeout = params.timeout.to_i <= 0 ? nil : params.timeout
    condition.wait(mutex, timeout)
  end

  unless opened
    ssh_channel.close

    raise ::Rex::ConnectionTimeout.new(params.peerhost, params.peerport) if failure_reason_code.nil?

    case failure_reason_code
    when ChannelFailureReason::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED
      reason = 'The SSH channel request was administratively prohibited.'
    when ChannelFailureReason::SSH_OPEN_UNKNOWN_CHANNEL_TYPE
      reason = 'The SSH channel type is not supported.'
    when ChannelFailureReason::SSH_OPEN_RESOURCE_SHORTAGE
      reason = 'The SSH channel request was denied because of a resource shortage.'
    end

    raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: reason)
  end
  msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, ssh_channel, params)
  sock = msf_channel.lsock

  # Notify now that we've created the socket
  notify_socket_created(self, sock, params)

  sock
end

#create_server_channel(params) ⇒ Object



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 290

def create_server_channel(params)
  msf_channel = nil
  mutex = Mutex.new
  condition = ConditionVariable.new
  timed_out = false
  @ssh_connection.send_global_request('tcpip-forward', :string, params.localhost, :long, params.localport) do |success, response|
    mutex.synchronize {
      remote_port = params.localport
      remote_port = response.read_long if remote_port == 0
      if success
        if timed_out
          # We're not using the port; clean it up
          elog("Remote forwarding on #{params.localhost}:#{params.localport} succeeded after timeout. Stopping channel to clean up dangling port")
          stop_server_channel(params.localhost, remote_port)
        else
          dlog("Remote forwarding from #{params.localhost} established on port #{remote_port}")
          key = [params.localhost, remote_port]
          msf_channel = TcpServerChannel.new(params, self, params.localhost, remote_port)
          @server_channels[key] = msf_channel
        end
      else
          elog("Remote forwarding failed on #{params.localhost}:#{params.localport}")
      end
      condition.signal
    }
  end

  mutex.synchronize {
    condition.wait(mutex, params.timeout)
    unless msf_channel
      timed_out = true
    end
  }

  # Return the server channel itself
  msf_channel
end

#descObject



252
253
254
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 252

def desc
  "SSH"
end

#on_got_remote_connection(_session, channel, packet) ⇒ Object

The SSH server has told us that there’s a port forwarding request. Find the relevant server channel and inform it.



403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 403

def on_got_remote_connection(_session, channel, packet)
  connected_address = packet.read_string
  connected_port = packet.read_long
  originator_address = packet.read_string
  originator_port = packet.read_long
  ilog("Received connection: #{connected_address}:#{connected_port} <--> #{originator_address}:#{originator_port}")
  # Find the correct TcpServerChannel
  #
  key = [connected_address, connected_port]
  server_channel = @server_channels[key]
  server_channel.create(@channel_ticker += 1, channel, originator_address, originator_port)
end

#stop_server_channel(host, port) ⇒ Object



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/msf/base/sessions/ssh_command_shell_bind.rb', line 328

def stop_server_channel(host, port)
  completed_event = Rex::Sync::Event.new
  dlog("Cancelling tcpip-forward to #{host}:#{port}")
  @ssh_connection.send_global_request('cancel-tcpip-forward', :string, host, :long, port) do |success, _response|
    if success
      key = [host, port]
      @server_channels.delete(key)
      ilog("Reverse SSH listener on #{host}:#{port} stopped")
    else
      elog("Could not stop reverse listener on #{host}:#{port}")
    end
    completed_event.set
  end
  timeout = 5 # seconds
  begin
    completed_event.wait(timeout)
    true
  rescue ::Timeout::Error
    false
  end
end