Path: blob/master/lib/msf/base/sessions/ssh_command_shell_bind.rb
19500 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')75rsock.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 opened143class TcpServerChannel144include Rex::IO::StreamServer145146def initialize(params, client, host, port)147@params = params148@client = client149@host = host150@port = port151@channels = []152@closed = false153@mutex = Mutex.new154@condition = ConditionVariable.new155156if params.ssl157extend(Rex::Socket::SslTcpServer)158initsock(params)159end160end161162def accept(opts = {})163timeout = opts['Timeout']164if (timeout.nil? || timeout <= 0)165timeout = nil166end167168@mutex.synchronize {169if @channels.length > 0170return _accept171end172@condition.wait(@mutex, timeout)173return _accept174}175end176177def closed?178@closed179end180181def close182if !closed?183@closed = @client.stop_server_channel(@host, @port)184end185end186187def create(cid, ssh_channel, peer_host, peer_port)188@mutex.synchronize {189peer_info = {190'PeerHost' => peer_host,191'PeerPort' => peer_port192}193params = @params.merge(peer_info)194channel = TcpClientChannel.new(@client, cid, ssh_channel, params)195@channels.insert(0, channel)196197# Let any waiting thread know we're ready198@condition.signal199}200end201202attr_reader :client203204protected205206def _accept207result = nil208channel = @channels.pop209if channel210result = channel.lsock211end212result213end214215end216217#218# Create a sessions instance from an SshConnection. This will handle creating219# a new command stream.220#221# @param ssh_connection [Net::SSH::Connection] The SSH connection to create a222# session instance for.223# @param opts [Hash] Optional parameters to pass to the session object.224def initialize(ssh_connection, opts = {})225@ssh_connection = ssh_connection226@sock = ssh_connection.transport.socket227@server_channels = {}228229initialize_channels230@channel_ticker = 0231232# Be alerted to reverse port forward connections (once we start listening on a port)233ssh_connection.on_open_channel('forwarded-tcpip', &method(:on_got_remote_connection))234super(nil, opts)235end236237def bootstrap(datastore = {}, handler = nil)238# this won't work after the rstream is initialized, so do it first239@platform = Metasploit::Framework::Ssh::Platform.get_platform(ssh_connection)240if @platform == 'windows'241extend(Msf::Sessions::WindowsEscaping)242elsif Metasploit::Framework::Ssh::Platform.is_posix(@platform)243extend(Msf::Sessions::UnixEscaping)244else245raise ::Net::SSH::Exception.new("Unknown platform: #{platform}")246end247248# if the platform is known, it was recovered by communicating with the device, so skip verification, also not all249# shells accessed through SSH may respond to the echo command issued for verification as expected250datastore['AutoVerifySession'] &= @platform.blank?251252@rstream = Net::SSH::CommandStream.new(ssh_connection, session: self, logger: self).lsock253super254255@info = "SSH #{username} @ #{@peer_info}"256end257258def desc259"SSH"260end261262#263# Create a network socket using this session. At this time, only TCP client264# connections can be made (like SSH port forwarding) while TCP server sockets265# can not be opened (SSH reverse port forwarding). The SSH specification does266# not define a UDP channel, so that is not supported either.267#268# @param params [Rex::Socket::Parameters] The parameters that should be used269# to open the socket.270#271# @raise [Rex::ConnectionError] If the connection fails, timesout or is not272# supported, a ConnectionError will be raised.273# @return [TcpClientChannel] The connected TCP client channel.274def create(params)275# Notify handlers before we create the socket276notify_before_socket_create(self, params)277278if params.proto == 'tcp'279if params.server280sock = create_server_channel(params)281else282sock = create_client_channel(params)283end284elsif params.proto == 'udp'285raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: 'UDP sockets are not supported by SSH sessions.')286end287288raise ::Rex::ConnectionError unless sock289290# Notify now that we've created the socket291notify_socket_created(self, sock, params)292293sock294end295296def supports_udp?297false298end299300def create_server_channel(params)301msf_channel = nil302mutex = Mutex.new303condition = ConditionVariable.new304timed_out = false305@ssh_connection.send_global_request('tcpip-forward', :string, params.localhost, :long, params.localport) do |success, response|306mutex.synchronize {307remote_port = params.localport308remote_port = response.read_long if remote_port == 0309if success310if timed_out311# We're not using the port; clean it up312elog("Remote forwarding on #{params.localhost}:#{params.localport} succeeded after timeout. Stopping channel to clean up dangling port")313stop_server_channel(params.localhost, remote_port)314else315dlog("Remote forwarding from #{params.localhost} established on port #{remote_port}")316key = [params.localhost, remote_port]317msf_channel = TcpServerChannel.new(params, self, params.localhost, remote_port)318@server_channels[key] = msf_channel319end320else321elog("Remote forwarding failed on #{params.localhost}:#{params.localport}")322end323condition.signal324}325end326327mutex.synchronize {328condition.wait(mutex, params.timeout)329unless msf_channel330timed_out = true331end332}333334# Return the server channel itself335msf_channel336end337338def stop_server_channel(host, port)339completed_event = Rex::Sync::Event.new340dlog("Cancelling tcpip-forward to #{host}:#{port}")341@ssh_connection.send_global_request('cancel-tcpip-forward', :string, host, :long, port) do |success, _response|342if success343key = [host, port]344@server_channels.delete(key)345ilog("Reverse SSH listener on #{host}:#{port} stopped")346else347elog("Could not stop reverse listener on #{host}:#{port}")348end349completed_event.set350end351timeout = 5 # seconds352begin353completed_event.wait(timeout)354true355rescue ::Timeout::Error356false357end358end359360def create_client_channel(params)361msf_channel = nil362mutex = Mutex.new363condition = ConditionVariable.new364opened = false365ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |_|366dlog("new direct-tcpip channel opened to #{Rex::Socket.is_ipv6?(params.peerhost) ? '[' + params.peerhost + ']' : params.peerhost}:#{params.peerport}")367opened = true368mutex.synchronize do369condition.signal370end371end372failure_reason_code = nil373ssh_channel.on_open_failed do |_ch, code, desc|374failure_reason_code = code375wlog("failed to open SSH channel (code: #{code.inspect}, description: #{desc.inspect})")376mutex.synchronize do377condition.signal378end379end380381mutex.synchronize do382timeout = params.timeout.to_i <= 0 ? nil : params.timeout383condition.wait(mutex, timeout)384end385386unless opened387ssh_channel.close388389raise ::Rex::ConnectionTimeout.new(params.peerhost, params.peerport) if failure_reason_code.nil?390391case failure_reason_code392when ChannelFailureReason::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED393reason = 'The SSH channel request was administratively prohibited.'394when ChannelFailureReason::SSH_OPEN_UNKNOWN_CHANNEL_TYPE395reason = 'The SSH channel type is not supported.'396when ChannelFailureReason::SSH_OPEN_RESOURCE_SHORTAGE397reason = 'The SSH channel request was denied because of a resource shortage.'398end399400raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: reason)401end402msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, ssh_channel, params)403sock = msf_channel.lsock404405# Notify now that we've created the socket406notify_socket_created(self, sock, params)407408sock409end410411# The SSH server has told us that there's a port forwarding request.412# Find the relevant server channel and inform it.413def on_got_remote_connection(_session, channel, packet)414connected_address = packet.read_string415connected_port = packet.read_long416originator_address = packet.read_string417originator_port = packet.read_long418ilog("Received connection: #{connected_address}:#{connected_port} <--> #{originator_address}:#{originator_port}")419# Find the correct TcpServerChannel420#421key = [connected_address, connected_port]422server_channel = @server_channels[key]423server_channel.create(@channel_ticker += 1, channel, originator_address, originator_port)424end425426def cleanup427channels.each_value(&:close)428@server_channels.each_value(&:close)429430super431end432433attr_reader :sock, :ssh_connection434end435end436437438