Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/msf/base/sessions/ssh_command_shell_bind.rb
Views: 11784
# -*- 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)240241# if the platform is known, it was recovered by communicating with the device, so skip verification, also not all242# shells accessed through SSH may respond to the echo command issued for verification as expected243datastore['AutoVerifySession'] &= @platform.blank?244245@rstream = Net::SSH::CommandStream.new(ssh_connection).lsock246super247248@info = "SSH #{username} @ #{@peer_info}"249end250251def desc252"SSH"253end254255#256# Create a network socket using this session. At this time, only TCP client257# connections can be made (like SSH port forwarding) while TCP server sockets258# can not be opened (SSH reverse port forwarding). The SSH specification does259# not define a UDP channel, so that is not supported either.260#261# @param params [Rex::Socket::Parameters] The parameters that should be used262# to open the socket.263#264# @raise [Rex::ConnectionError] If the connection fails, timesout or is not265# supported, a ConnectionError will be raised.266# @return [TcpClientChannel] The connected TCP client channel.267def create(params)268# Notify handlers before we create the socket269notify_before_socket_create(self, params)270271if params.proto == 'tcp'272if params.server273sock = create_server_channel(params)274else275sock = create_client_channel(params)276end277elsif params.proto == 'udp'278raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: 'UDP sockets are not supported by SSH sessions.')279end280281raise ::Rex::ConnectionError unless sock282283# Notify now that we've created the socket284notify_socket_created(self, sock, params)285286sock287end288289def supports_udp?290false291end292293def create_server_channel(params)294msf_channel = nil295mutex = Mutex.new296condition = ConditionVariable.new297timed_out = false298@ssh_connection.send_global_request('tcpip-forward', :string, params.localhost, :long, params.localport) do |success, response|299mutex.synchronize {300remote_port = params.localport301remote_port = response.read_long if remote_port == 0302if success303if timed_out304# We're not using the port; clean it up305elog("Remote forwarding on #{params.localhost}:#{params.localport} succeeded after timeout. Stopping channel to clean up dangling port")306stop_server_channel(params.localhost, remote_port)307else308dlog("Remote forwarding from #{params.localhost} established on port #{remote_port}")309key = [params.localhost, remote_port]310msf_channel = TcpServerChannel.new(params, self, params.localhost, remote_port)311@server_channels[key] = msf_channel312end313else314elog("Remote forwarding failed on #{params.localhost}:#{params.localport}")315end316condition.signal317}318end319320mutex.synchronize {321condition.wait(mutex, params.timeout)322unless msf_channel323timed_out = true324end325}326327# Return the server channel itself328msf_channel329end330331def stop_server_channel(host, port)332completed_event = Rex::Sync::Event.new333dlog("Cancelling tcpip-forward to #{host}:#{port}")334@ssh_connection.send_global_request('cancel-tcpip-forward', :string, host, :long, port) do |success, _response|335if success336key = [host, port]337@server_channels.delete(key)338ilog("Reverse SSH listener on #{host}:#{port} stopped")339else340elog("Could not stop reverse listener on #{host}:#{port}")341end342completed_event.set343end344timeout = 5 # seconds345begin346completed_event.wait(timeout)347true348rescue ::Timeout::Error349false350end351end352353def create_client_channel(params)354msf_channel = nil355mutex = Mutex.new356condition = ConditionVariable.new357opened = false358ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |_|359dlog("new direct-tcpip channel opened to #{Rex::Socket.is_ipv6?(params.peerhost) ? '[' + params.peerhost + ']' : params.peerhost}:#{params.peerport}")360opened = true361mutex.synchronize do362condition.signal363end364end365failure_reason_code = nil366ssh_channel.on_open_failed do |_ch, code, desc|367failure_reason_code = code368wlog("failed to open SSH channel (code: #{code.inspect}, description: #{desc.inspect})")369mutex.synchronize do370condition.signal371end372end373374mutex.synchronize do375timeout = params.timeout.to_i <= 0 ? nil : params.timeout376condition.wait(mutex, timeout)377end378379unless opened380ssh_channel.close381382raise ::Rex::ConnectionTimeout.new(params.peerhost, params.peerport) if failure_reason_code.nil?383384case failure_reason_code385when ChannelFailureReason::SSH_OPEN_ADMINISTRATIVELY_PROHIBITED386reason = 'The SSH channel request was administratively prohibited.'387when ChannelFailureReason::SSH_OPEN_UNKNOWN_CHANNEL_TYPE388reason = 'The SSH channel type is not supported.'389when ChannelFailureReason::SSH_OPEN_RESOURCE_SHORTAGE390reason = 'The SSH channel request was denied because of a resource shortage.'391end392393raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: reason)394end395msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, ssh_channel, params)396sock = msf_channel.lsock397398# Notify now that we've created the socket399notify_socket_created(self, sock, params)400401sock402end403404# The SSH server has told us that there's a port forwarding request.405# Find the relevant server channel and inform it.406def on_got_remote_connection(_session, channel, packet)407connected_address = packet.read_string408connected_port = packet.read_long409originator_address = packet.read_string410originator_port = packet.read_long411ilog("Received connection: #{connected_address}:#{connected_port} <--> #{originator_address}:#{originator_port}")412# Find the correct TcpServerChannel413#414key = [connected_address, connected_port]415server_channel = @server_channels[key]416server_channel.create(@channel_ticker += 1, channel, originator_address, originator_port)417end418419def cleanup420channels.each_value(&:close)421@server_channels.each_value(&:close)422423super424end425426attr_reader :sock, :ssh_connection427end428end429430431