Skip to content

Commit

Permalink
Land #19, externalize db_export command
Browse files Browse the repository at this point in the history
  • Loading branch information
mkienow-r7 committed Mar 23, 2018
2 parents f5b1d27 + ed5b22a commit eb47962
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ module DataProxyAutoLoader
autoload :SessionEventDataProxy, 'metasploit/framework/data_service/proxy/session_event_data_proxy'
autoload :CredentialDataProxy, 'metasploit/framework/data_service/proxy/credential_data_proxy'
autoload :NmapDataProxy, 'metasploit/framework/data_service/proxy/nmap_data_proxy'
autoload :DbExportDataProxy, 'metasploit/framework/data_service/proxy/db_export_data_proxy'
autoload :VulnAttemptDataProxy, 'metasploit/framework/data_service/proxy/vuln_attempt_data_proxy'

include ServiceDataProxy
include HostDataProxy
include VulnDataProxy
Expand All @@ -30,5 +32,6 @@ module DataProxyAutoLoader
include SessionEventDataProxy
include CredentialDataProxy
include NmapDataProxy
include DbExportDataProxy
include VulnAttemptDataProxy
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module DbExportDataProxy
def run_db_export(path, format)
begin
data_service = self.get_data_service
opts = {
path: path,
format: format
}
data_service.run_db_export(opts)
rescue Exception => e
self.log_error(e, "Problem generating DB Export")
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ module DataServiceAutoLoader
autoload :RemoteSessionEventDataService, 'metasploit/framework/data_service/remote/http/remote_session_event_data_service'
autoload :RemoteCredentialDataService, 'metasploit/framework/data_service/remote/http/remote_credential_data_service'
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'

include RemoteHostDataService
include RemoteEventDataService
include RemoteNoteDataService
Expand All @@ -29,5 +31,6 @@ module DataServiceAutoLoader
include RemoteSessionEventDataService
include RemoteCredentialDataService
include RemoteNmapDataService
include RemoteDbExportDataService
include RemoteVulnAttemptDataService
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require 'metasploit/framework/data_service/remote/http/response_data_helper'

module RemoteDbExportDataService
include ResponseDataHelper

DB_EXPORT_API_PATH = '/api/v1/db-export'

def run_db_export(opts)
response = json_to_hash(self.get_data(DB_EXPORT_API_PATH, nil, opts))

process_file(response[:db_export_file], opts[:path])
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,34 @@
#
module ResponseDataHelper

#
# Converts an HTTP response to a Hash
#
# @param [ResponseWrapper] A wrapped HTTP response containing a JSON body.
# @return [Hash] A Hash interpretation of the JSON body.
#
def json_to_hash(response_wrapper)
begin
if response_wrapper.expected
body = response_wrapper.response.body
unless body.nil? && body.empty?
return JSON.parse(body).symbolize_keys
end
end
rescue Exception => e
elog "Error parsing response: #{e.message}"
e.backtrace.each { |line| elog line }
end
end

#
# Converts an HTTP response to an OpenStruct object
#
def json_to_open_struct_object(response_wrapper, returns_on_error = nil)
if response_wrapper.expected
begin
body = response_wrapper.response.body
if not body.nil? and not body.empty?
if !body.nil? && !body.empty?
return JSON.parse(body, object_class: OpenStruct)
end
rescue Exception => e
Expand All @@ -31,11 +51,12 @@ def json_to_open_struct_object(response_wrapper, returns_on_error = nil)
# @param [String] The Mdm class to convert the JSON to.
# @param [Anything] A failsafe response to return if no objects are found.
# @return [ActiveRecord::Base] An object of type mdm_class, which inherits from ActiveRecord::Base
#
def json_to_mdm_object(response_wrapper, mdm_class, returns_on_error = nil)
if response_wrapper.expected
begin
body = response_wrapper.response.body
if not body.nil? and not body.empty?
if !body.nil? && !body.empty?
parsed_body = Array.wrap(JSON.parse(body))
rv = []
parsed_body.each do |json_object|
Expand Down Expand Up @@ -135,4 +156,4 @@ def open_struct(hash)
OpenStruct.new(hash)
end

end
end
36 changes: 20 additions & 16 deletions lib/msf/core/db_export.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class Export

attr_accessor :workspace

STATUS_START = "start"
STATUS_COMPLETE = "complete"

def initialize(workspace)
self.workspace = workspace
end
Expand All @@ -31,70 +34,72 @@ def host_allowed?(arg)

# Performs an export of the workspace's `Metasploit::Credential::Login` objects in pwdump format
# @param path [String] the path on the local filesystem where the exported data will be written
# @return [void]
# @return [String] The path to the location of the written file.
def to_pwdump_file(path, &block)
exporter = Metasploit::Credential::Exporter::Pwdump.new(workspace: workspace)

File.open(path, 'w') do |file|
output_file = File.open(path, 'w') do |file|
file << exporter.rendered_output
end
true
output_file.path
end


# Performs an export of the workspace's `Metasploit::Credential::Login` objects in XML format
# @param path [String] the path on the local filesystem where the exported data will be written
# @return [String] The path to the location of the written file.
def to_xml_file(path, &block)

yield(:status, "start", "report") if block_given?
yield(:status, STATUS_START, "report") if block_given?
extract_target_entries
report_file = ::File.open(path, "wb")

report_file.write %Q|<?xml version="1.0" encoding="UTF-8"?>\n|
report_file.write %Q|<MetasploitV5>\n|
report_file.write %Q|<generated time="#{Time.now.utc}" user="#{myusername}" project="#{myworkspace.name.gsub(/[^A-Za-z0-9\x20]/n,"_")}" product="framework"/>\n|

yield(:status, "start", "hosts") if block_given?
yield(:status, STATUS_START, "hosts") if block_given?
report_file.write %Q|<hosts>\n|
report_file.flush
extract_host_info(report_file)
report_file.write %Q|</hosts>\n|

yield(:status, "start", "events") if block_given?
yield(:status, STATUS_START, "events") if block_given?
report_file.write %Q|<events>\n|
report_file.flush
extract_event_info(report_file)
report_file.write %Q|</events>\n|

yield(:status, "start", "services") if block_given?
yield(:status, STATUS_START, "services") if block_given?
report_file.write %Q|<services>\n|
report_file.flush
extract_service_info(report_file)
report_file.write %Q|</services>\n|

yield(:status, "start", "web sites") if block_given?
yield(:status, STATUS_START, "web sites") if block_given?
report_file.write %Q|<web_sites>\n|
report_file.flush
extract_web_site_info(report_file)
report_file.write %Q|</web_sites>\n|

yield(:status, "start", "web pages") if block_given?
yield(:status, STATUS_START, "web pages") if block_given?
report_file.write %Q|<web_pages>\n|
report_file.flush
extract_web_page_info(report_file)
report_file.write %Q|</web_pages>\n|

yield(:status, "start", "web forms") if block_given?
yield(:status, STATUS_START, "web forms") if block_given?
report_file.write %Q|<web_forms>\n|
report_file.flush
extract_web_form_info(report_file)
report_file.write %Q|</web_forms>\n|

yield(:status, "start", "web vulns") if block_given?
yield(:status, STATUS_START, "web vulns") if block_given?
report_file.write %Q|<web_vulns>\n|
report_file.flush
extract_web_vuln_info(report_file)
report_file.write %Q|</web_vulns>\n|

yield(:status, "start", "module details") if block_given?
yield(:status, STATUS_START, "module details") if block_given?
report_file.write %Q|<module_details>\n|
report_file.flush
extract_module_detail_info(report_file)
Expand All @@ -105,9 +110,9 @@ def to_xml_file(path, &block)
report_file.flush
report_file.close

yield(:status, "complete", "report") if block_given?
yield(:status, STATUS_COMPLETE, "report") if block_given?

true
report_file.path
end

# A convenience function that bundles together host, event, and service extraction.
Expand Down Expand Up @@ -543,4 +548,3 @@ def extract_web_vuln_info(report_file)
end
end
end

2 changes: 2 additions & 0 deletions lib/msf/core/db_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Msf::DBManager
autoload :Client, 'msf/core/db_manager/client'
autoload :Connection, 'msf/core/db_manager/connection'
autoload :Cred, 'msf/core/db_manager/cred'
autoload :DbExport, 'msf/core/db_manager/db_export'
autoload :Event, 'msf/core/db_manager/event'
autoload :ExploitAttempt, 'msf/core/db_manager/exploit_attempt'
autoload :ExploitedHost, 'msf/core/db_manager/exploited_host'
Expand Down Expand Up @@ -68,6 +69,7 @@ class Msf::DBManager
include Msf::DBManager::Client
include Msf::DBManager::Connection
include Msf::DBManager::Cred
include Msf::DBManager::DbExport
include Msf::DBManager::Event
include Msf::DBManager::ExploitAttempt
include Msf::DBManager::ExploitedHost
Expand Down
20 changes: 20 additions & 0 deletions lib/msf/core/db_manager/db_export.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'msf/core/db_export'

module Msf::DBManager::DbExport
def run_db_export(opts)
exporter = Msf::DBManager::Export.new(framework.db.workspace)

output_file = exporter.send("to_#{opts[:format]}_file".intern, opts[:path]) do |mtype, mstatus, mname|
if mtype == :status
if mstatus == Msf::DBManager::Export::STATUS_START
ilog " >> Starting export of #{mname}"
end
if mstatus == Msf::DBManager::Export::STATUS_COMPLETE
ilog " >> Finished export of #{mname}"
end
end
end

File.expand_path(output_file)
end
end
35 changes: 35 additions & 0 deletions lib/msf/core/db_manager/http/servlet/db_export_servlet.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module DbExportServlet

def self.api_path
'/api/v1/db-export'
end

def self.registered(app)
app.get DbExportServlet.api_path, &get_db_export
end

#######
private
#######

def self.get_db_export
lambda {
begin
opts = params.symbolize_keys
opts[:path] = File.join(Msf::Config.local_directory, "#{File.basename(opts[:path])}-#{SecureRandom.hex}")

output_file = get_db.run_db_export(opts)

encoded_file = Base64.urlsafe_encode64(File.read(File.expand_path(output_file)))
response = {}
response[:db_export_file] = encoded_file
set_json_response(response)
rescue Exception => e
set_error_on_response(e)
ensure
# Ensure the temporary file gets cleaned up
File.delete(opts[:path])
end
}
end
end
2 changes: 2 additions & 0 deletions lib/msf/core/db_manager/http/sinatra_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
require 'msf/core/db_manager/http/servlet/session_event_servlet'
require 'msf/core/db_manager/http/servlet/credential_servlet'
require 'msf/core/db_manager/http/servlet/nmap_servlet'
require 'msf/core/db_manager/http/servlet/db_export_servlet'
require 'msf/core/db_manager/http/servlet/vuln_attempt_servlet'

class SinatraApp < Sinatra::Base
Expand All @@ -35,5 +36,6 @@ class SinatraApp < Sinatra::Base
register SessionEventServlet
register CredentialServlet
register NmapServlet
register DbExportServlet
register VulnAttemptServlet
end
13 changes: 1 addition & 12 deletions lib/msf/ui/console/command_dispatcher/db.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1523,18 +1523,7 @@ def cmd_db_export(*args)
end

print_status("Starting export of workspace #{framework.db.workspace.name} to #{output} [ #{format} ]...")
exporter = ::Msf::DBManager::Export.new(framework.db.workspace)

exporter.send("to_#{format}_file".intern,output) do |mtype, mstatus, mname|
if mtype == :status
if mstatus == "start"
print_status(" >> Starting export of #{mname}")
end
if mstatus == "complete"
print_status(" >> Finished export of #{mname}")
end
end
end
framework.db.run_db_export(output, format)
print_status("Finished export of workspace #{framework.db.workspace.name} to #{output} [ #{format} ]...")
}
end
Expand Down

0 comments on commit eb47962

Please sign in to comment.