ruby/lib/metasploit/aggregator/connection_manager.rb
require 'openssl'
require 'singleton'
require 'socket'
require 'metasploit/aggregator/logger'
require 'metasploit/aggregator/http_forwarder'
require 'metasploit/aggregator/https_forwarder'
require 'metasploit/aggregator/cable'
module Metasploit
module Aggregator
class ConnectionManager
include Singleton
def initialize
@cables = []
@manager_mutex = Mutex.new
@router = Router.instance
@details_cache = SessionDetailService.instance
@connection_cleaner = Thread.new { flush_connections }
end
def self.ssl_generate_certificate
yr = 24*3600*365
vf = Time.at(Time.now.to_i - rand(yr * 3) - yr)
vt = Time.at(vf.to_i + (10 * yr))
cn = 'localhost'
key = OpenSSL::PKey::RSA.new(2048){ }
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = (rand(0xFFFFFFFF) << 32) + rand(0xFFFFFFFF)
cert.subject = OpenSSL::X509::Name.new([["CN", cn]])
cert.issuer = OpenSSL::X509::Name.new([["CN", cn]])
cert.not_before = vf
cert.not_after = vt
cert.public_key = key.public_key
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
cert.extensions = [
ef.create_extension("basicConstraints","CA:FALSE")
]
ef.issuer_certificate = cert
cert.sign(key, OpenSSL::Digest::SHA256.new)
[key, cert, nil]
end
def ssl_parse_certificate(certificate)
key, cert, chain = nil
unless certificate.nil?
begin
# parse the cert
key = OpenSSL::PKey::RSA.new(certificate)
cert = OpenSSL::X509::Certificate.new(certificate)
# TODO: ensure this parses all certificate in object provided
rescue OpenSSL::PKey::RSAError => e
Logger.log(e.message)
end
end
return key, cert, chain
end
def add_cable_https(host, port, certificate)
@manager_mutex.synchronize do
forwarder = Metasploit::Aggregator::HttpsForwarder.new
forwarder.log_messages = true
server = TCPServer.new(host, port)
ssl_context = OpenSSL::SSL::SSLContext.new
unless certificate.nil?
ssl_context.key, ssl_context.cert = ssl_parse_certificate(certificate)
else
ssl_context.key, ssl_context.cert = Metasploit::Aggregator::ConnectionManager.ssl_generate_certificate
end
ssl_server = OpenSSL::SSL::SSLServer.new(server, ssl_context)
handler = connect_cable(ssl_server, host, port, forwarder)
@cables << Cable.new(handler, server, forwarder)
handler
end
end
def add_cable_http(host, port)
@manager_mutex.synchronize do
forwarder = Metasploit::Aggregator::HttpForwarder.new
forwarder.log_messages = true
server = TCPServer.new(host, port)
handler = connect_cable(server, host, port, forwarder)
@cables << Cable.new(handler, server, forwarder)
end
end
def register_forward(uuid, payload_list = nil)
if payload_list.nil?
@router.add_route(uuid, nil)
else
payload_list.each do |payload|
@router.add_route(uuid, payload)
end
end
end
def connections
connections = {}
@cables.each do |cable|
connections = connections.merge cable.forwarder.connections
end
connections
end
def connection_details(payload)
detail_map = {}
details = @details_cache.session_details(payload)
unless details.nil?
details.each_pair do |key, value|
detail_map[key] = value.to_s
end
end
@cables.each do |cable|
next unless cable.forwarder.connections.include?(payload)
# TODO: improve how time is exposed for live connections
time = cable.forwarder.connection_info(payload)['TIME']
detail_map['LAST_SEEN'] = (Time.now - time).to_s unless time.nil?
end
detail_map
end
def cables
local_cables = []
@cables.each do |cable|
addr = cable.server.local_address
local_cables << addr.ip_address + ':' + addr.ip_port.to_s
end
local_cables
end
def connect_cable(server, host, port, forwarder)
Logger.log "Listening on port #{host}:#{port}"
handler = Thread.new do
begin
loop do
Logger.log "waiting for connection on #{host}:#{port}"
connection = server.accept
Logger.log "got connection on #{host}:#{port}"
Thread.new do
begin
forwarder.forward(connection)
rescue
Logger.log $!
end
Logger.log "completed connection on #{host}:#{port}"
end
end
end
end
handler
end
def remove_cable(host, port)
@manager_mutex.synchronize do
closed_servers = []
@cables.each do |cable|
addr = cable.server.local_address
if addr.ip_address == host && addr.ip_port == port.to_i
cable.server.close
cable.thread.exit
closed_servers << cable
end
end
@cables -= closed_servers
end
return true
end
def stop
@manager_mutex.synchronize do
@cables.each do |listener|
listener.server.close
listener.thread.exit
end
end
@connection_cleaner.exit
end
def park(payload)
@router.add_route(nil, payload)
Logger.log "parking #{payload}"
end
def flush_connections
while true
@cables.each do |cable|
# this relies on a side effect that accessing clears stale connections
cable.forwarder.connections.length
end
sleep 10
end
end
private :ssl_parse_certificate
end
end
end