Initial work on reverse pivoting through SSH
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user