Path: blob/master/lib/msf/base/sessions/ssh_command_shell_bind.rb
56915 views
# -*- coding: binary -*-12require 'metasploit/framework/ssh/platform'3require 'rex/post/channel'4require 'rex/post/meterpreter/channels/socket_abstraction'56module Msf::Sessions7#8# This class provides a session for SSH client connections, where Metasploit9# has authenticated to a remote SSH server. It is compatible with the10# Net::SSH library.11#12class SshCommandShellBind < Msf::Sessions::CommandShell1314include Msf::Session::Comm15include Rex::Post::Channel::Container1617# see: https://datatracker.ietf.org/doc/html/rfc4254#section-5.118module ChannelFailureReason19SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 120SSH_OPEN_CONNECT_FAILED = 221SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 322SSH_OPEN_RESOURCE_SHORTAGE = 423end2425#26# This is a Metasploit Framework channel object that wraps a Net::SSH native27# channel object.28#29class TcpClientChannel30include Rex::Post::Channel::StreamAbstraction3132#33# This is a common interface that socket paris are extended with to be34# compatible with pivoting.35#36module SocketInterface37include Rex::Post::Channel::SocketAbstraction::SocketInterface3839def type?40'tcp'41end42end4344#45# Create a new TcpClientChannel instance.46#47# @param client [SshCommandShellBind] The command shell session that this48# channel instance belongs to.49# @param cid [Integer] The channel ID.50# @param ssh_channel [Net::SSH::Connection::Channel] The connected SSH51# channel.52# @param params [Rex::Socket::Parameters] The parameters that were used to53# open the channel.54def initialize(client, cid, ssh_channel, params)55initialize_abstraction5657@client = client58@cid = cid59@ssh_channel = ssh_channel60@params = params61@mutex = Mutex.new6263ssh_channel.on_close do |_ch|64dlog('ssh_channel#on_close closing the sock')65close66end6768ssh_channel.on_data do |_ch, data|69# dlog("ssh_channel#on_data received #{data.length} bytes")70rsock.syswrite(data)71end7273ssh_channel.on_eof do |_ch|74dlog('ssh_channel#on_eof shutting down the socket')75lsock.shutdown(Socket::SHUT_WR)76end7778lsock.extend(SocketInterface)79lsock.channel = self8081rsock.extend(SocketInterface)82rsock.channel = self8384lsock.extend(Rex::Socket::SslTcp) if params.ssl && !params.server8586# synchronize access so the socket isn't closed while initializing, this is particularly important for SSL87lsock.synchronize_access { lsock.initsock(params) }88rsock.synchronize_access { rsock.initsock(params) }8990client.add_channel(self)91end9293def closed?94@cid.nil?95end9697def close98cid = @cid99@mutex.synchronize do100return if closed?101102@cid = nil103end104105@client.remove_channel(cid)106cleanup_abstraction107@ssh_channel.close108end109110def close_write111if closed?112raise IOError, 'Channel has been closed.', caller113end114115@ssh_channel.eof!116end117118#119# Write *buf* to the channel, optionally truncating it to *length* bytes.120#121# @param [String] buf The data to write to the channel.122# @param [Integer] length An optional length to truncate *data* to before123# sending it.124def write(buf, length = nil)125if closed?126raise IOError, 'Channel has been closed.', caller127end128129if !length.nil? && buf.length >= length130buf = buf[0..length]131end132133@ssh_channel.send_data(buf)134buf.length135end136137attr_reader :cid, :client, :params138end139140# Represents an SSH reverse port forward.141# Will receive connection messages back from the SSH server,142# whereupon a TcpClientChannel will be opened143144class TcpServerChannel145include Rex::IO::StreamServer146147attr_reader :params148149def initialize(params, client)150@params = params151@client = client152@channels = []153@closed = false154@mutex = Mutex.new155@condition = ConditionVariable.new156157if params.ssl158extend(Rex::Socket::SslTcpServer)159initsock(params)160end161end162163def accept(opts = {})164timeout = opts['Timeout']165if (timeout.nil? || timeout <= 0)166timeout = nil167end168169@mutex.synchronize {170if @channels.length > 0171return _accept172end173@condition.wait(@mutex, timeout)174return _accept175}176end177178def closed?179@closed180end181182def close183if !closed?184@closed = @client.stop_server_channel(@params.localhost, @params.localport)185end186end187188def create(cid, ssh_channel, peer_host, peer_port)189@mutex.synchronize {190peer_info = {191'PeerHost' => peer_host,192'PeerPort' => peer_port193}194params = @params.merge(peer_info)195channel = TcpClientChannel.new(@client, cid, ssh_channel, params)196@channels.insert(0, channel)197198# Let any waiting thread know we're ready199@condition.signal200}201end202203attr_reader :client204205protected206207def _accept208result = nil209channel = @channels.pop210if channel211result = channel.lsock212end213result214end215216end217218#219# Create a sessions instance from an SshConnection. This will handle creating220# a new command stream.221#222# @param ssh_connection [Net::SSH::Connection] The SSH connection to create a223# session instance for.224# @param opts [Hash] Optional parameters to pass to the session object.225def initialize(ssh_connection, opts = {})226@ssh_connection = ssh_connection227@sock = ssh_connection.transport.socket228@server_channels = {}229230initialize_channels231@channel_ticker = 0232233# Be alerted to reverse port forward connections (once we start listening on a port)234ssh_connection.on_open_channel('forwarded-tcpip', &method(:on_got_remote_connection))235super(nil, opts)236end237238def bootstrap(datastore = {}, handler = nil)239# this won't work after the rstream is initialized, so do it first240@platform = Metasploit::Framework::Ssh::Platform.get_platform(ssh_connection)241if @platform == 'windows'242extend(Msf::Sessions::WindowsEscaping)243elsif Metasploit::Framework::Ssh::Platform.is_posix(@platform)244extend(Msf::Sessions::UnixEscaping)245else246raise ::Net::SSH::Exception.new("Unknown platform: #{platform}")247end248249# if the platform is known, it was recovered by communicating with the device, so skip verification, also not all250# shells accessed through SSH may respond to the echo command issued for verification as expected251datastore['AutoVerifySession'] &= @platform.blank?252253@rstream = Net::SSH::CommandStream.new(ssh_connection, session: self, logger: self).lsock254super255256@info = "SSH #{username} @ #{@peer_info}"257end258259def desc260"SSH"261end262263#264# Create a network socket using this session. At this time, only TCP client265# connections can be made (like SSH port forwarding) while TCP server sockets266# can not be opened (SSH reverse port forwarding). The SSH specification does267# not define a UDP channel, so that is not supported either.268#269# @param params [Rex::Socket::Parameters] The parameters that should be used270# to open the socket.271#272# @raise [Rex::ConnectionError] If the connection fails, timesout or is not273# supported, a ConnectionError will be raised.274# @return [TcpClientChannel] The connected TCP client channel.275def create(params)276# Notify handlers before we create the socket277notify_before_socket_create(self, params)278279if params.proto == 'tcp'280if params.server281sock = create_server_channel(params)282else283sock = create_client_channel(params)284end285elsif params.proto == 'udp'286raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: 'UDP sockets are not supported by SSH sessions.')287end288289raise ::Rex::ConnectionError unless sock290291# Notify now that we've created the socket292notify_socket_created(self, sock, params)293294sock295end296297def supports_udp?298false299end300301def create_server_channel(params)302msf_channel = nil303mutex = Mutex.new304condition = ConditionVariable.new305timed_out = false306@ssh_connection.send_global_request('tcpip-forward', :string, params.localhost, :long, params.localport) do |success, response|307mutex.synchronize {308if params.localport == 0309params.localport = response.read_long310end311312if success313if timed_out314# We're not using the port; clean it up315elog("Remote forwarding on #{Rex::Socket.to_authority(params.localhost, params.localport)} succeeded after timeout, stopping channel to clean up dangling port")316stop_server_channel(params.localhost, params.localport)317else318dlog("Remote forwarding from #{params.localhost} established on port #{params.localport}")319key = [params.localhost, params.localport]320msf_channel = TcpServerChannel.new(params, self)321@server_channels[key] = msf_channel322end323else324elog("Remote forwarding failed on #{Rex::Socket.to_authority(params.localhost, params.localport)}")325end326condition.signal327}328end329330mutex.synchronize {331condition.wait(mutex, params.timeout)332unless msf_channel333timed_out = true334end335}336337# Return the server channel itself338msf_channel339end340341def stop_server_channel(host, port)342completed_event = Rex::Sync::Event.new343dlog("Cancelling tcpip-forward to #{host}:#{port}")344@ssh_connection.send_global_request('cancel-tcpip-forward', :string, host, :long, port) do |success, _response|345if success346key = [host, port]347@server_channels.delete(key)348ilog("Reverse SSH listener on #{host}:#{port} stopped")349else350elog("Could not stop reverse listener on #{host}:#{port}")351end352completed_event.set353end354timeout = 5 # seconds355begin356completed_event.wait(timeout)357true358rescue ::Timeout::Error359false360end361end362363def create_client_channel(params)364msf_channel = nil365mutex = Mutex.new366condition = ConditionVariable.new367opened = false368ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |_|369dlog("new direct-tcpip channel opened to #{Rex::Socket.to_authority(params.peerhost, params.peerport)}")370opened = true371mutex.synchronize do372condition.signal373end374end375failure_reason_code = nil376ssh_channel.on_open_failed do |_ch, code, desc|377failure_reason_code = code378wlog("failed to open SSH channel (code: #{code.inspect}, description: #{desc.inspect})")379mutex.synchronize do380condition.signal381end382end383384mutex.synchronize do385timeout = params.timeout.to_i <= 0 ? nil : params.timeout386condition.wait(mutex, timeout)387end388389unless opened390ssh_channel.close391392raise ::Rex::ConnectionTimeout.new(params.peerhost, params.peerport) if failure_reason_code.nil?393394case failure_reason_code395when ChannelFailureReason::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED396reason = 'The SSH channel request was administratively prohibited.'397when ChannelFailureReason::SSH_OPEN_UNKNOWN_CHANNEL_TYPE398reason = 'The SSH channel type is not supported.'399when ChannelFailureReason::SSH_OPEN_RESOURCE_SHORTAGE400reason = 'The SSH channel request was denied because of a resource shortage.'401end402403raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: reason)404end405msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, ssh_channel, params)406sock = msf_channel.lsock407408# Notify now that we've created the socket409notify_socket_created(self, sock, params)410411sock412end413414# The SSH server has told us that there's a port forwarding request.415# Find the relevant server channel and inform it.416def on_got_remote_connection(_session, channel, packet)417connected_address = packet.read_string418connected_port = packet.read_long419originator_address = packet.read_string420originator_port = packet.read_long421ilog("Received connection: #{connected_address}:#{connected_port} <--> #{originator_address}:#{originator_port}")422# Find the correct TcpServerChannel423#424key = [connected_address, connected_port]425server_channel = @server_channels[key]426server_channel.create(@channel_ticker += 1, channel, originator_address, originator_port)427end428429def cleanup430channels.each_value(&:close)431@server_channels.each_value(&:close)432433super434end435436attr_reader :sock, :ssh_connection437end438end439440441