Files
metasploit-gs/lib/msf/base/sessions/ssh_command_shell_bind.rb
T

216 lines
5.9 KiB
Ruby
Raw Normal View History

2021-06-15 17:35:19 -04:00
# -*- coding: binary -*-
2021-06-17 15:58:09 -04:00
# todo: refactor this so it's no longer under Meterpreter so it can be used elsewhere
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
class SshCommandShellBind < Msf::Sessions::CommandShell
include Msf::Session::Comm
include Rex::Post::Channel::Container
2021-06-15 17:35:19 -04:00
2021-06-29 18:29:14 -04:00
class TcpClientChannel
2021-06-15 17:35:19 -04:00
include Rex::IO::StreamAbstraction
2021-06-30 09:00: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.
2021-06-28 17:43:57 -04:00
def initialize(client, cid, ssh_channel, params)
2021-06-15 17:35:19 -04:00
initialize_abstraction
2021-06-16 17:40:08 -04:00
@client = client
2021-06-28 17:43:57 -04:00
@cid = cid
2021-06-28 16:19:01 -04:00
@ssh_channel = ssh_channel
2021-06-16 17:40:08 -04:00
@params = params
2021-07-13 16:54:09 -04:00
@mutex = Mutex.new
2021-06-15 17:35:19 -04:00
2021-06-28 16:19:01 -04:00
ssh_channel.on_close do |ch|
2021-06-29 18:29:14 -04:00
dlog("ssh_channel#on_close closing sock")
2021-07-13 16:54:09 -04:00
close
2021-06-28 16:19:01 -04:00
end
ssh_channel.on_data do |ch, data|
2021-06-29 18:29:14 -04:00
#dlog("ssh_channel#on_data received #{data.length} bytes")
rsock.syswrite(data)
2021-06-28 16:19:01 -04:00
end
ssh_channel.on_eof do |ch|
2021-06-29 18:29:14 -04:00
dlog("ssh_channel#on_eof closing sock")
2021-07-13 16:54:09 -04:00
rsock.shutdown(Socket::SHUT_WR)
2021-06-28 16:19:01 -04:00
end
lsock.extend(Rex::Post::Channel::SocketAbstraction::SocketInterface)
2021-06-15 17:35:19 -04:00
lsock.channel = self
rsock.extend(Rex::Post::Channel::SocketAbstraction::SocketInterface)
2021-06-15 17:35:19 -04:00
rsock.channel = self
2021-06-28 17:43:57 -04:00
client.add_channel(self)
end
2021-06-29 09:31:57 -04:00
def closed?
@cid.nil?
end
2021-06-28 17:43:57 -04:00
def close
2021-07-13 16:54:09 -04:00
@mutex.synchronize {
return if closed?
cid = @cid
@cid = nil
}
2021-06-29 09:31:57 -04:00
2021-07-13 16:54:09 -04:00
@client.remove_channel(cid)
2021-06-28 17:43:57 -04:00
cleanup_abstraction
@ssh_channel.close
2021-06-15 17:35:19 -04:00
end
2021-06-29 10:28:54 -04:00
#
# Read *length* bytes from the channel. If the operation times out, the data
# that was read will be returned or nil if no data was read.
#
2021-06-17 15:58:09 -04:00
def read(length = nil)
2021-06-29 10:28:54 -04:00
if @cid.nil?
raise IOError, 'Channel has been closed.', caller
end
buf = ''
length = 65536 if length.nil?
begin
while buf.length < length
buf << lsock.recv(length - buf.length)
end
rescue StandardError
buf = nil if buf.empty?
end
buf
2021-06-17 15:58:09 -04:00
end
2021-06-29 10:28:54 -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 @cid.nil?
raise IOError, 'Channel has been closed.', caller
end
if !length.nil? && buf.length >= length
buf = buf[0..length]
end
@ssh_channel.send_data(buf)
buf.length
2021-06-15 17:35:19 -04:00
end
2021-06-28 17:43:57 -04:00
attr_reader :cid
2021-06-16 17:40:08 -04:00
attr_reader :client
2021-06-15 17:35:19 -04:00
attr_reader :params
end
2021-06-28 17:43:57 -04:00
def initialize(ssh_connection, rstream, opts = {})
@ssh_connection = ssh_connection
@sock = ssh_connection.transport.socket
initialize_channels
@channel_ticker = 0
super(rstream, opts)
end
2021-06-30 09:00: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)
2021-06-15 17:35:19 -04:00
# Notify handlers before we create the socket
2021-06-30 09:00:13 -04:00
notify_before_socket_create(self, params)
2021-06-15 17:35:19 -04:00
2021-06-28 16:19:01 -04:00
mutex = Mutex.new
condition = ConditionVariable.new
2021-06-29 10:28:54 -04:00
ssh_channel = msf_channel = nil
2021-06-28 16:19:01 -04:00
2021-07-09 17:14:54 -04:00
if params.proto == 'tcp'
if params.server
raise ::Rex::BindFailed.new(params.localhost, params.localport, reason: 'TCP server sockets are not supported by SSH sessions.')
end
2021-06-30 09:00:13 -04:00
ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |new_channel|
msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, new_channel, params)
2021-06-28 16:19:01 -04:00
mutex.synchronize {
condition.signal
}
2021-06-15 17:35:19 -04:00
end
2021-07-09 17:14:54 -04:00
elsif params.proto == 'udp'
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: 'UDP sockets are not supported by SSH sessions.')
2021-06-15 17:35:19 -04:00
end
2021-06-28 16:19:01 -04:00
raise ::Rex::ConnectionError.new if ssh_channel.nil?
2021-06-15 17:35:19 -04:00
2021-06-28 16:19:01 -04:00
ssh_channel.on_open_failed do |ch, code, desc|
2021-06-29 18:29:14 -04:00
wlog("failed to open SSH channel (code=#{code.inspect}, description=#{desc.inspect})")
2021-06-28 16:19:01 -04:00
mutex.synchronize {
condition.signal
}
2021-06-15 17:35:19 -04:00
end
2021-06-28 16:19:01 -04:00
mutex.synchronize {
2021-06-30 09:00:13 -04:00
condition.wait(mutex, params.timeout)
2021-06-28 16:19:01 -04:00
}
2021-06-15 17:35:19 -04:00
2021-07-09 17:14:54 -04:00
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport) if msf_channel.nil?
2021-06-28 16:19:01 -04:00
sock = msf_channel.lsock
2021-06-15 17:35:19 -04:00
2021-06-28 16:19:01 -04:00
# Notify now that we've created the socket
2021-06-30 09:00:13 -04:00
notify_socket_created(self, sock, params)
2021-07-13 16:54:09 -04:00
2021-06-28 16:19:01 -04:00
sock
2021-06-15 17:35:19 -04:00
end
2021-06-29 09:31:57 -04:00
def cleanup
channels.values.each do |channel|
channel.close
end
super
end
attr_reader :sock
attr_reader :ssh_connection
2021-06-15 17:35:19 -04:00
2021-06-30 09:00: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.
#
# @return [SshCommandShellBind] A new session instance.
def self.from_ssh_socket(ssh_connection, opts = {})
command_stream = Net::SSH::CommandStream.new(ssh_connection)
self.new(ssh_connection, command_stream.lsock, opts)
2021-06-15 17:35:19 -04:00
end
end
end