2021-06-15 17:35:19 -04:00
# -*- coding: binary -*-
2021-10-22 17:24:26 -04:00
require 'metasploit/framework/ssh/platform'
2021-06-30 09:19:17 -04:00
require 'rex/post/channel'
2021-06-17 15:58:09 -04:00
require 'rex/post/meterpreter/channels/socket_abstraction'
2021-06-15 17:35:19 -04:00
module Msf::Sessions
2021-07-23 10:58:13 -04:00
#
# 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.
#
class SshCommandShellBind < Msf :: Sessions :: CommandShell
2021-06-15 17:35:19 -04:00
2021-07-23 10:58:13 -04:00
include Msf :: Session :: Comm
include Rex :: Post :: Channel :: Container
2021-07-14 11:00:34 -04:00
2021-07-23 10:58:13 -04:00
# see: https://datatracker.ietf.org/doc/html/rfc4254#section-5.1
module ChannelFailureReason
SSH_OPEN_ADMINISTRATIVELY_PROHIBITED = 1
SSH_OPEN_CONNECT_FAILED = 2
SSH_OPEN_UNKNOWN_CHANNEL_TYPE = 3
SSH_OPEN_RESOURCE_SHORTAGE = 4
2021-07-14 11:00:34 -04:00
end
2021-06-30 09:00:13 -04:00
#
2021-07-23 10:58:13 -04:00
# This is a Metasploit Framework channel object that wraps a Net::SSH native
# channel object.
2021-06-30 09:00:13 -04:00
#
2021-07-23 10:58:13 -04:00
class TcpClientChannel
2021-09-24 14:18:55 -04:00
include Rex :: Post :: Channel :: StreamAbstraction
2021-07-23 10:58:13 -04:00
#
# This is a common interface that socket paris are extended with to be
# compatible with pivoting.
#
module SocketInterface
include Rex :: Post :: Channel :: SocketAbstraction :: SocketInterface
def type?
'tcp'
end
2021-06-28 16:19:01 -04:00
end
2021-07-23 10:58:13 -04:00
#
# Create a new TcpClientChannel instance.
#
# @param client [SshCommandShellBind] The command shell session that this
# channel instance belongs to.
# @param cid [Integer] The channel ID.
# @param ssh_channel [Net::SSH::Connection::Channel] The connected SSH
# channel.
# @param params [Rex::Socket::Parameters] The parameters that were used to
# open the channel.
def initialize ( client , cid , ssh_channel , params )
initialize_abstraction
@client = client
@cid = cid
@ssh_channel = ssh_channel
@params = params
@mutex = Mutex . new
ssh_channel . on_close do | _ch |
dlog ( 'ssh_channel#on_close closing the sock' )
close
end
2021-06-28 16:19:01 -04:00
2021-07-23 10:58:13 -04:00
ssh_channel . on_data do | _ch , data |
# dlog("ssh_channel#on_data received #{data.length} bytes")
rsock . syswrite ( data )
end
2021-06-28 16:19:01 -04:00
2021-07-23 10:58:13 -04:00
ssh_channel . on_eof do | _ch |
dlog ( 'ssh_channel#on_eof shutting down the socket' )
2026-02-10 16:12:11 -05:00
lsock . shutdown ( Socket :: SHUT_WR )
2021-07-23 10:58:13 -04:00
end
2021-06-15 17:35:19 -04:00
2021-07-23 10:58:13 -04:00
lsock . extend ( SocketInterface )
lsock . channel = self
2021-06-28 17:43:57 -04:00
2021-07-23 10:58:13 -04:00
rsock . extend ( SocketInterface )
rsock . channel = self
2021-06-28 17:43:57 -04:00
2021-10-25 12:08:36 -04:00
lsock . extend ( Rex :: Socket :: SslTcp ) if params . ssl && ! params . server
2021-09-29 09:51:04 -04:00
# synchronize access so the socket isn't closed while initializing, this is particularly important for SSL
lsock . synchronize_access { lsock . initsock ( params ) }
rsock . synchronize_access { rsock . initsock ( params ) }
2021-09-28 15:49:36 -04:00
2021-07-23 10:58:13 -04:00
client . add_channel ( self )
end
def closed?
@cid . nil?
end
2021-06-29 09:31:57 -04:00
2021-07-23 10:58:13 -04:00
def close
cid = @cid
@mutex . synchronize do
return if closed?
2021-06-29 09:31:57 -04:00
2021-07-23 10:58:13 -04:00
@cid = nil
end
2021-06-15 17:35:19 -04:00
2021-07-23 10:58:13 -04:00
@client . remove_channel ( cid )
cleanup_abstraction
@ssh_channel . close
2021-07-14 11:00:34 -04:00
end
2021-07-23 10:58:13 -04:00
def close_write
if closed?
raise IOError , 'Channel has been closed.' , caller
end
2021-07-14 11:00:34 -04:00
2021-07-23 10:58:13 -04:00
@ssh_channel . eof!
2021-06-29 10:28:54 -04:00
end
2021-07-23 10:58:13 -04:00
#
# Write *buf* to the channel, optionally truncating it to *length* bytes.
#
# @param [String] buf The data to write to the channel.
# @param [Integer] length An optional length to truncate *data* to before
# sending it.
def write ( buf , length = nil )
if closed?
raise IOError , 'Channel has been closed.' , caller
end
2021-06-17 15:58:09 -04:00
2021-07-23 10:58:13 -04:00
if ! length . nil? && buf . length > = length
buf = buf [ 0 .. length ]
end
2021-06-29 10:28:54 -04:00
2021-07-23 10:58:13 -04:00
@ssh_channel . send_data ( buf )
buf . length
2021-06-29 10:28:54 -04:00
end
2021-07-23 10:58:13 -04:00
attr_reader :cid , :client , :params
2021-06-15 17:35:19 -04:00
end
2021-09-15 14:34:04 +10:00
# Represents an SSH reverse port forward.
# Will receive connection messages back from the SSH server,
# whereupon a TcpClientChannel will be opened
2026-02-10 15:04:51 -05:00
2021-09-14 16:40:58 +10:00
class TcpServerChannel
2021-10-02 14:26:44 +10:00
include Rex :: IO :: StreamServer
2026-02-10 15:04:51 -05:00
attr_reader :params
def initialize ( params , client )
2021-09-14 16:40:58 +10:00
@params = params
@client = client
2021-09-17 09:47:11 +10:00
@channels = [ ]
2021-09-15 10:29:43 +10:00
@closed = false
2021-09-17 09:47:11 +10:00
@mutex = Mutex . new
@condition = ConditionVariable . new
2021-10-25 12:08:36 -04:00
if params . ssl
extend ( Rex :: Socket :: SslTcpServer )
initsock ( params )
end
2021-09-14 16:40:58 +10:00
end
def accept ( opts = { } )
timeout = opts [ 'Timeout' ]
if ( timeout . nil? || timeout < = 0 )
2021-09-17 09:47:11 +10:00
timeout = nil
2021-09-14 16:40:58 +10:00
end
2021-09-17 09:47:11 +10:00
@mutex . synchronize {
if @channels . length > 0
return _accept
2021-09-15 14:34:04 +10:00
end
2021-09-17 09:47:11 +10:00
@condition . wait ( @mutex , timeout )
return _accept
}
2021-09-14 16:40:58 +10:00
end
2021-09-15 10:29:43 +10:00
def closed?
@closed
end
2021-09-14 16:40:58 +10:00
def close
2021-09-15 10:29:43 +10:00
if ! closed?
2026-02-10 15:04:51 -05:00
@closed = @client . stop_server_channel ( @params . localhost , @params . localport )
2021-09-15 10:29:43 +10:00
end
2021-09-14 16:40:58 +10:00
end
2021-09-15 14:28:15 +10:00
def create ( cid , ssh_channel , peer_host , peer_port )
2021-09-17 09:47:11 +10:00
@mutex . synchronize {
peer_info = {
'PeerHost' = > peer_host ,
'PeerPort' = > peer_port
}
params = @params . merge ( peer_info )
channel = TcpClientChannel . new ( @client , cid , ssh_channel , params )
@channels . insert ( 0 , channel )
# Let any waiting thread know we're ready
@condition . signal
2021-09-15 14:28:15 +10:00
}
2021-09-14 16:40:58 +10:00
end
2022-01-24 14:41:59 -05:00
attr_reader :client
2021-09-15 14:34:04 +10:00
protected
2021-09-14 16:40:58 +10:00
2021-09-17 09:47:11 +10:00
def _accept
2021-09-14 16:40:58 +10:00
result = nil
2021-09-17 09:47:11 +10:00
channel = @channels . pop
if channel
result = channel . lsock
2021-09-14 16:40:58 +10:00
end
result
end
end
2021-07-23 10:58:13 -04:00
#
# Create a sessions instance from an SshConnection. This will handle creating
# a new command stream.
#
# @param ssh_connection [Net::SSH::Connection] The SSH connection to create a
# session instance for.
# @param opts [Hash] Optional parameters to pass to the session object.
def initialize ( ssh_connection , opts = { } )
@ssh_connection = ssh_connection
@sock = ssh_connection . transport . socket
2021-09-14 16:40:58 +10:00
@server_channels = { }
2021-07-23 10:58:13 -04:00
initialize_channels
@channel_ticker = 0
2021-09-14 16:40:58 +10:00
# 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 ) )
2021-11-10 16:52:10 -05:00
super ( nil , opts )
2021-07-23 10:58:13 -04:00
end
2021-06-15 17:35:19 -04:00
2021-10-22 17:24:26 -04:00
def bootstrap ( datastore = { } , handler = nil )
2021-11-10 16:52:10 -05:00
# this won't work after the rstream is initialized, so do it first
@platform = Metasploit :: Framework :: Ssh :: Platform . get_platform ( ssh_connection )
2024-12-18 10:42:52 +11:00
if @platform == 'windows'
extend ( Msf :: Sessions :: WindowsEscaping )
2025-01-10 10:40:22 +11:00
elsif Metasploit :: Framework :: Ssh :: Platform . is_posix ( @platform )
2024-12-18 10:42:52 +11:00
extend ( Msf :: Sessions :: UnixEscaping )
2025-03-05 18:16:31 +11:00
else
raise :: Net :: SSH :: Exception . new ( " Unknown platform: #{ platform } " )
2024-12-18 10:42:52 +11:00
end
2021-10-22 17:24:26 -04:00
# 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?
2021-11-10 16:52:10 -05:00
2024-11-18 17:32:48 +00:00
@rstream = Net :: SSH :: CommandStream . new ( ssh_connection , session : self , logger : self ) . lsock
2021-10-22 17:24:26 -04:00
super
@info = " SSH #{ username } @ #{ @peer_info } "
end
def desc
" SSH "
end
2021-07-23 10:58:13 -04:00
#
# 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.
#
# @param params [Rex::Socket::Parameters] The parameters that should be used
# to open the socket.
#
# @raise [Rex::ConnectionError] If the connection fails, timesout or is not
# supported, a ConnectionError will be raised.
# @return [TcpClientChannel] The connected TCP client channel.
def create ( params )
# Notify handlers before we create the socket
notify_before_socket_create ( self , params )
if params . proto == 'tcp'
2021-09-15 10:29:43 +10:00
if params . server
sock = create_server_channel ( params )
else
sock = create_client_channel ( params )
end
2021-07-23 10:58:13 -04:00
elsif params . proto == 'udp'
raise :: Rex :: ConnectionError . new ( params . peerhost , params . peerport , reason : 'UDP sockets are not supported by SSH sessions.' )
2021-07-09 17:14:54 -04:00
end
2021-09-14 16:40:58 +10:00
raise :: Rex :: ConnectionError unless sock
# Notify now that we've created the socket
notify_socket_created ( self , sock , params )
sock
end
2023-11-07 15:33:10 +11:00
def supports_udp?
false
end
2021-09-14 16:40:58 +10:00
def create_server_channel ( params )
msf_channel = nil
2021-09-22 08:10:05 +10:00
mutex = Mutex . new
condition = ConditionVariable . new
timed_out = false
2021-09-14 16:40:58 +10:00
@ssh_connection . send_global_request ( 'tcpip-forward' , :string , params . localhost , :long , params . localport ) do | success , response |
2021-09-22 08:10:05 +10:00
mutex . synchronize {
2026-02-10 15:04:51 -05:00
if params . localport == 0
params . localport = response . read_long
end
2021-09-22 08:10:05 +10:00
if success
if timed_out
# We're not using the port; clean it up
2026-02-10 16:12:11 -05:00
elog ( " Remote forwarding on #{ Rex :: Socket . to_authority ( params . localhost , params . localport ) } succeeded after timeout, stopping channel to clean up dangling port " )
2026-02-10 15:04:51 -05:00
stop_server_channel ( params . localhost , params . localport )
2021-09-22 08:10:05 +10:00
else
2026-02-10 15:04:51 -05:00
dlog ( " Remote forwarding from #{ params . localhost } established on port #{ params . localport } " )
key = [ params . localhost , params . localport ]
msf_channel = TcpServerChannel . new ( params , self )
2021-09-22 08:10:05 +10:00
@server_channels [ key ] = msf_channel
end
else
2026-02-10 15:04:51 -05:00
elog ( " Remote forwarding failed on #{ Rex :: Socket . to_authority ( params . localhost , params . localport ) } " )
2021-09-22 08:10:05 +10:00
end
2021-09-24 16:28:30 +10:00
condition . signal
2021-09-22 08:10:05 +10:00
}
2021-09-15 10:29:43 +10:00
end
2021-09-22 08:10:05 +10:00
mutex . synchronize {
condition . wait ( mutex , params . timeout )
unless msf_channel
timed_out = true
end
}
2021-09-14 16:40:58 +10:00
# Return the server channel itself
2021-09-15 14:34:04 +10:00
msf_channel
2021-09-14 16:40:58 +10:00
end
def stop_server_channel ( host , port )
2021-09-15 10:29:43 +10:00
completed_event = Rex :: Sync :: Event . new
dlog ( " Cancelling tcpip-forward to #{ host } : #{ port } " )
2021-09-15 14:34:04 +10:00
@ssh_connection . send_global_request ( 'cancel-tcpip-forward' , :string , host , :long , port ) do | success , _response |
2021-09-14 16:40:58 +10:00
if success
key = [ host , port ]
@server_channels . delete ( key )
2021-09-17 09:48:34 +10:00
ilog ( " Reverse SSH listener on #{ host } : #{ port } stopped " )
2021-09-14 16:40:58 +10:00
else
2021-09-17 09:48:34 +10:00
elog ( " Could not stop reverse listener on #{ host } : #{ port } " )
2021-09-14 16:40:58 +10:00
end
2021-09-15 10:29:43 +10:00
completed_event . set
2021-09-14 16:40:58 +10:00
end
timeout = 5 # seconds
2021-09-15 10:29:43 +10:00
begin
completed_event . wait ( timeout )
true
2022-04-23 02:40:02 +01:00
rescue :: Timeout :: Error
2021-09-15 10:29:43 +10:00
false
end
2021-09-14 16:40:58 +10:00
end
2021-07-23 10:58:13 -04:00
2021-09-14 16:40:58 +10:00
def create_client_channel ( params )
2021-09-15 10:29:43 +10:00
msf_channel = nil
2021-09-14 16:40:58 +10:00
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 | _ |
2026-02-10 15:04:51 -05:00
dlog ( " new direct-tcpip channel opened to #{ Rex :: Socket . to_authority ( params . peerhost , params . peerport ) } " )
2021-09-14 16:40:58 +10:00
opened = true
mutex . synchronize do
condition . signal
end
end
2021-07-23 10:58:13 -04:00
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
2021-06-28 16:19:01 -04:00
condition . signal
2021-07-23 10:58:13 -04:00
end
2021-06-15 17:35:19 -04:00
end
2021-07-23 10:58:13 -04:00
mutex . synchronize do
2021-09-28 15:49:36 -04:00
timeout = params . timeout . to_i < = 0 ? nil : params . timeout
condition . wait ( mutex , timeout )
2021-07-23 10:58:13 -04:00
end
2021-06-15 17:35:19 -04:00
2021-09-28 15:49:36 -04:00
unless opened
2021-07-23 10:58:13 -04:00
ssh_channel . close
2021-06-15 17:35:19 -04:00
2021-07-23 10:58:13 -04:00
raise :: Rex :: ConnectionTimeout . new ( params . peerhost , params . peerport ) if failure_reason_code . nil?
2021-07-14 14:38:33 -04:00
2021-07-23 10:58:13 -04:00
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
2021-07-14 14:38:33 -04:00
2021-07-23 10:58:13 -04:00
raise :: Rex :: ConnectionError . new ( params . peerhost , params . peerport , reason : reason )
2021-07-14 14:38:33 -04:00
end
2021-09-28 15:49:36 -04:00
msf_channel = TcpClientChannel . new ( self , @channel_ticker += 1 , ssh_channel , params )
2021-07-23 10:58:13 -04:00
sock = msf_channel . lsock
2021-06-15 17:35:19 -04:00
2021-07-23 10:58:13 -04:00
# Notify now that we've created the socket
notify_socket_created ( self , sock , params )
2021-06-15 17:35:19 -04:00
2021-07-23 10:58:13 -04:00
sock
2021-06-29 09:31:57 -04:00
end
2021-09-14 16:40:58 +10:00
# The SSH server has told us that there's a port forwarding request.
# Find the relevant server channel and inform it.
2021-09-15 14:34:04 +10:00
def on_got_remote_connection ( _session , channel , packet )
2021-09-14 16:40:58 +10:00
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 ]
2021-09-15 14:28:15 +10:00
server_channel . create ( @channel_ticker += 1 , channel , originator_address , originator_port )
2021-09-14 16:40:58 +10:00
end
2021-07-23 10:58:13 -04:00
def cleanup
channels . each_value ( & :close )
2021-09-15 10:29:43 +10:00
@server_channels . each_value ( & :close )
2021-06-29 09:31:57 -04:00
2021-07-23 10:58:13 -04:00
super
end
2021-06-15 17:35:19 -04:00
2021-07-23 10:58:13 -04:00
attr_reader :sock , :ssh_connection
end
2021-06-15 17:35:19 -04:00
end