Initial work on reverse pivoting through SSH

This commit is contained in:
Ashley Donaldson
2021-09-14 16:40:58 +10:00
parent 25b41c9174
commit fdfecdc8ff
+137 -15
View File
@@ -138,6 +138,59 @@ module Msf::Sessions
attr_reader :cid, :client, :params
end
class TcpServerChannel
def initialize(params, client, host, port)
@params = params
@client = client
@host = host
@port = port
@channels = Queue.new
end
def accept(opts = {})
timeout = opts['Timeout']
if (timeout.nil? || timeout <= 0)
timeout = 0
end
result = nil
begin
::Timeout.timeout(timeout) {
result = _accept
}
rescue Timeout::Error
end
result
end
def close
@client.stop_server_channel(@host, @port)
end
def create(cid, ssh_channel)
channel = TcpClientChannel.new(@client, cid, ssh_channel, @params)
@channels.enq(channel)
end
protected
def _accept(nonblock = false)
result = nil
begin
channel = @channels.deq(nonblock)
if channel
result = channel.lsock
end
rescue ThreadError
# This happens when there are no clients in the queue
end
result
end
end
#
# Create a sessions instance from an SshConnection. This will handle creating
# a new command stream.
@@ -148,11 +201,15 @@ module Msf::Sessions
def initialize(ssh_connection, opts = {})
@ssh_connection = ssh_connection
@sock = ssh_connection.transport.socket
@server_channels = {}
initialize_channels
@channel_ticker = 0
rstream = Net::SSH::CommandStream.new(ssh_connection).lsock
# 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))
super(rstream, opts)
end
@@ -172,29 +229,79 @@ module Msf::Sessions
# Notify handlers before we create the socket
notify_before_socket_create(self, params)
mutex = Mutex.new
condition = ConditionVariable.new
ssh_channel = msf_channel = nil
opened = false
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
ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |_|
dlog("new direct-tcpip channel opened to #{Rex::Socket.is_ipv6?(params.peerhost) ? '[' + params.peerhost + ']' : params.peerhost}:#{params.peerport}")
opened = true
mutex.synchronize do
condition.signal
if params.server
sock = create_server_channel(params)
else
sock = create_client_channel(params)
end
end
elsif params.proto == 'udp'
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: 'UDP sockets are not supported by SSH sessions.')
end
raise ::Rex::ConnectionError if ssh_channel.nil?
raise ::Rex::ConnectionError unless sock
# Notify now that we've created the socket
notify_socket_created(self, sock, params)
sock
end
def create_server_channel(params)
keep_trying = true
msf_channel = nil
@ssh_connection.send_global_request('tcpip-forward', :string, params.localhost, :long, params.localport) do |success, response|
if success
remote_port = params.localport
remote_port = response.read_long if remote_port == 0
dlog("Remote forwarding from #{params.localhost} established on port #{remote_port}")
key = [params.localhost, remote_port]
msf_channel = TcpServerChannel.new(params, self, params.localhost, remote_port)
@server_channels[key] = msf_channel
else
print_error("Remote forwarding failed on #{params.localhost}:#{params.localport}")
end
keep_trying = false
end
@ssh_connection.loop(params.timeout) {
keep_trying
}
# Return the server channel itself
sock = msf_channel
end
def stop_server_channel(host, port)
keep_trying = true
@ssh_connection.send_global_request("cancel-tcpip-forward", :string, host, :long, port) do |success, response|
if success
key = [host, port]
@server_channels.delete(key)
keep_trying = false
else
keep_trying = false
raise Rex::ConnectionError, "Could not stop reverse listener on #{host}:#{port}"
end
end
timeout = 5 # seconds
@ssh_connection.loop(timeout) {
keep_trying
}
end
def create_client_channel(params)
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 |_|
dlog("new direct-tcpip channel opened to #{Rex::Socket.is_ipv6?(params.peerhost) ? '[' + params.peerhost + ']' : params.peerhost}:#{params.peerport}")
opened = true
mutex.synchronize do
condition.signal
end
end
failure_reason_code = nil
ssh_channel.on_open_failed do |_ch, code, desc|
failure_reason_code = code
@@ -225,7 +332,6 @@ module Msf::Sessions
raise ::Rex::ConnectionError.new(params.peerhost, params.peerport, reason: reason)
end
msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, ssh_channel, params)
sock = msf_channel.lsock
@@ -235,8 +341,24 @@ module Msf::Sessions
sock
end
# The SSH server has told us that there's a port forwarding request.
# Find the relevant server channel and inform it.
def on_got_remote_connection(session, channel, packet)
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]
server_channel.create(channel)
end
def cleanup
channels.each_value(&:close)
server_channels.each_value(&:cleanup)
super
end