Land #9926, check remote data service before connecting

This PR adds a check prior to connecting to a remote data service
to verify it is online and returning expected data. This prevents
crashes that were occurring when unexpected responses were returned
This commit is contained in:
James Barnett 2018-04-25 14:07:33 -05:00
commit a5172e066d
No known key found for this signature in database
GPG Key ID: 647983861A4EC5EA
12 changed files with 118 additions and 50 deletions

View File

@ -6,8 +6,10 @@ require 'metasploit/framework/data_service/stubs/note_data_service'
require 'metasploit/framework/data_service/stubs/web_data_service'
require 'metasploit/framework/data_service/stubs/service_data_service'
require 'metasploit/framework/data_service/stubs/session_data_service'
require 'metasploit/framework/data_service/stubs/session_event_service'
require 'metasploit/framework/data_service/stubs/exploit_data_service'
require 'metasploit/framework/data_service/stubs/loot_data_service'
require 'metasploit/framework/data_service/stubs/msf_data_service'
#
# All data service implementations should include this module to ensure proper implementation
@ -23,8 +25,10 @@ module DataService
include NoteDataService
include ServiceDataService
include SessionDataService
include SessionEventDataService
include ExploitDataService
include LootDataService
include MsfDataService
def name
raise 'DataService#name is not implemented';
@ -34,6 +38,10 @@ module DataService
raise 'DataService#active is not implemented';
end
def active=(value)
raise 'DataService#active= is not implemented';
end
def is_local?
raise 'DataService#is_local? is not implemented';
end

View File

@ -52,30 +52,36 @@ class DataProxy
end
#
# Registers a data service with the proxy and immediately
# set as primary if online
# Registers the specified data service with the proxy
# and immediately sets it as the primary if active
#
def register_data_service(data_service, online=false)
def register_data_service(data_service)
validate(data_service)
data_service_id = @data_service_id += 1
@data_services[data_service_id] = data_service
set_data_service(data_service_id, online)
set_data_service(data_service_id)
end
#
# Set the data service to be used
#
def set_data_service(data_service_id, online=false)
def set_data_service(data_service_id)
data_service = @data_services[data_service_id.to_i]
if data_service.nil?
raise "Data service with id: #{data_service_id} does not exist"
end
if !online && !data_service.active
raise "Data service not online: #{data_service.name}, not setting as active"
if !data_service.is_local? && !data_service.active
raise "Data service #{data_service.name} is not online, and won't be set as active"
end
prev_data_service = @current_data_service
@current_data_service = data_service
# reset the previous data service's active flag if it is remote
# to ensure checks are performed the next time it is set
if !prev_data_service.nil? && !prev_data_service.is_local?
prev_data_service.active = false
end
end
#
@ -154,12 +160,12 @@ class DataProxy
begin
db_manager = opts.delete(:db_manager)
if !db_manager.nil?
register_data_service(db_manager, true)
register_data_service(db_manager)
@usable = true
else
@error = 'disabled'
end
rescue Exception => e
rescue => e
raise "Unable to initialize data service: #{e.message}"
end
end

View File

@ -19,6 +19,7 @@ module DataProxyAutoLoader
autoload :DbExportDataProxy, 'metasploit/framework/data_service/proxy/db_export_data_proxy'
autoload :DbImportDataProxy, 'metasploit/framework/data_service/proxy/db_import_data_proxy'
autoload :VulnAttemptDataProxy, 'metasploit/framework/data_service/proxy/vuln_attempt_data_proxy'
autoload :MsfDataProxy, 'metasploit/framework/data_service/proxy/msf_data_proxy'
include ServiceDataProxy
include HostDataProxy
@ -36,4 +37,5 @@ module DataProxyAutoLoader
include DbExportDataProxy
include DbImportDataProxy
include VulnAttemptDataProxy
include MsfDataProxy
end

View File

@ -0,0 +1,10 @@
module MsfDataProxy
def get_msf_version
begin
data_service = self.get_data_service
data_service.get_msf_version
rescue => e
self.log_error(e, "Problem retrieving Metasploit version")
end
end
end

View File

@ -25,6 +25,7 @@ class RemoteHTTPDataService
# @param [String] endpoint A valid http or https URL. Cannot be nil
#
def initialize(endpoint, framework, https_opts = {})
@active = false
validate_endpoint(endpoint)
@endpoint = URI.parse(endpoint)
@https_opts = https_opts
@ -40,6 +41,26 @@ class RemoteHTTPDataService
end
def name
"remote_data_service: (#{@endpoint})"
end
def active
# checks if data service is online when @active is falsey and makes the assignment
# this is to prevent repetitive calls to check if data service is online
# logic should be enhanced to considering data service connectivity
# and future data service implementations
@active ||= is_online?
end
def active=(value)
@active = value
end
def is_local?
false
end
def error
'none'
end
@ -152,7 +173,7 @@ class RemoteHTTPDataService
rescue EOFError => e
elog "No data was returned from the data service for request type/path : #{request_type}/#{path}, message: #{e.message}"
return FailedResponse.new('')
rescue Exception => e
rescue => e
elog "Problem with HTTP request for type/path: #{request_type}/#{path} message: #{e.message}"
return FailedResponse.new('')
ensure
@ -160,21 +181,6 @@ class RemoteHTTPDataService
end
end
#
# TODO: fix this
#
def active
return true
end
def name
"remote_data_service: (#{@endpoint})"
end
def is_local?
false
end
def set_header(key, value)
@headers = Hash.new() if @headers.nil?
@ -224,6 +230,19 @@ class RemoteHTTPDataService
raise 'Endpoint cannot be nil' if endpoint.nil?
end
#
# Checks if the data service is online by making a request
# for the Metasploit version number from the remote endpoint
#
def is_online?
response = self.get_msf_version
if response && !response[:metasploit_version].empty?
return true
end
return false
end
def build_request(request, data_hash)
request.content_type = 'application/json'
if !data_hash.nil? && !data_hash.empty?

View File

@ -17,6 +17,7 @@ module DataServiceAutoLoader
autoload :RemoteNmapDataService, 'metasploit/framework/data_service/remote/http/remote_nmap_data_service'
autoload :RemoteDbExportDataService, 'metasploit/framework/data_service/remote/http/remote_db_export_data_service'
autoload :RemoteVulnAttemptDataService, 'metasploit/framework/data_service/remote/http/remote_vuln_attempt_data_service'
autoload :RemoteMsfDataService, 'metasploit/framework/data_service/remote/http/remote_msf_data_service'
include RemoteHostDataService
include RemoteEventDataService
@ -33,4 +34,5 @@ module DataServiceAutoLoader
include RemoteNmapDataService
include RemoteDbExportDataService
include RemoteVulnAttemptDataService
include RemoteMsfDataService
end

View File

@ -0,0 +1,12 @@
require 'metasploit/framework/data_service/remote/http/response_data_helper'
module RemoteMsfDataService
include ResponseDataHelper
MSF_API_PATH = '/api/v1/msf'
MSF_VERSION_API_PATH = "#{MSF_API_PATH}/version"
def get_msf_version
json_to_hash(self.get_data(MSF_VERSION_API_PATH, nil, nil))
end
end

View File

@ -0,0 +1,5 @@
module MsfDataService
def get_msf_version
raise 'MsfDataService#get_msf_version is not implemented'
end
end

View File

@ -0,0 +1,25 @@
module MsfServlet
def self.api_path
'/api/v1/msf'
end
def self.api_version_path
"#{MsfServlet.api_path}/version"
end
def self.registered(app)
app.get MsfServlet.api_version_path, &get_msf_version
end
#######
private
#######
def self.get_msf_version
lambda {
set_json_response({metasploit_version: Metasploit::Framework::VERSION})
}
end
end

View File

@ -1,21 +0,0 @@
module OnlineTestServlet
def self.api_path
'/api/v1/online'
end
def self.registered(app)
app.get OnlineTestServlet.api_path, &get_active
end
#######
private
#######
def self.get_active
lambda {
set_empty_response()
}
end
end

View File

@ -5,7 +5,7 @@ require 'msf/core/db_manager/http/servlet/note_servlet'
require 'msf/core/db_manager/http/servlet/vuln_servlet'
require 'msf/core/db_manager/http/servlet/event_servlet'
require 'msf/core/db_manager/http/servlet/web_servlet'
require 'msf/core/db_manager/http/servlet/online_test_servlet'
require 'msf/core/db_manager/http/servlet/msf_servlet'
require 'msf/core/db_manager/http/servlet/workspace_servlet'
require 'msf/core/db_manager/http/servlet/service_servlet'
require 'msf/core/db_manager/http/servlet/session_servlet'
@ -26,7 +26,7 @@ class SinatraApp < Sinatra::Base
register VulnServlet
register EventServlet
register WebServlet
register OnlineTestServlet
register MsfServlet
register NoteServlet
register WorkspaceServlet
register ServiceServlet

View File

@ -1994,7 +1994,7 @@ class Db
framework.db.register_data_service(remote_data_service)
print_line "Registered data service: #{remote_data_service.name}"
framework.db.workspace = framework.db.default_workspace
rescue Exception => e
rescue => e
print_error "There was a problem registering the remote data service: #{e.message}"
end
end
@ -2004,7 +2004,7 @@ class Db
data_service = framework.db.set_data_service(service_id)
framework.db.workspace = framework.db.default_workspace
data_service
rescue Exception => e
rescue => e
print_error "Unable to set data service: #{e.message}"
end
end