Skip to content

Commit b81e9a4

Browse files
committed
Pass 1: externalize database
1 parent b7b1995 commit b81e9a4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1850
-117
lines changed

Gemfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ source 'https://rubygems.org'
33
# spec.add_runtime_dependency '<name>', [<version requirements>]
44
gemspec name: 'metasploit-framework'
55

6+
gem 'thin'
7+
gem 'sinatra'
8+
gem 'ruby-prof'
69
gem 'bit-struct', git: 'https://github.com/busterb/bit-struct', branch: 'ruby-2.4'
710
gem 'method_source', git: 'https://github.com/banister/method_source', branch: 'master'
811

@@ -24,6 +27,8 @@ group :development do
2427
# metasploit-aggregator as a framework only option for now
2528
# Metasploit::Aggregator external session proxy
2629
gem 'metasploit-aggregator'
30+
31+
#gem 'rex-core', path: '/home/chlee/rapid7/rex-core'
2732
end
2833

2934
group :development, :test do

lib/metasploit/framework/command/base.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ class Metasploit::Framework::Command::Base
4444
#
4545
# @return (see parsed_options)
4646
def self.require_environment!
47+
# TODO: Look into removing Rails.application (save ~20mb)
48+
# return self.parsed_options if ( self.parsed_options.options.database.remote_process)
49+
4750
parsed_options = self.parsed_options
51+
4852
# RAILS_ENV must be set before requiring 'config/application.rb'
4953
parsed_options.environment!
5054
ARGV.replace(parsed_options.positional)
@@ -79,7 +83,9 @@ def self.parsed_options_class_name
7983

8084
def self.start
8185
parsed_options = require_environment!
82-
new(application: Rails.application, parsed_options: parsed_options).start
86+
is_db_remote = false # parsed_options.options.database.remote_process
87+
application = is_db_remote ? nil : Rails.application
88+
new(application: application, parsed_options: parsed_options).start
8389
end
8490

8591
#

lib/metasploit/framework/command/console.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def driver_options
7979
driver_options['DatabaseEnv'] = options.environment
8080
driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths
8181
driver_options['DatabaseYAML'] = options.database.config
82+
driver_options['DatabaseRemoteProcess'] = options.database.remote_process
8283
driver_options['DeferModuleLoads'] = options.modules.defer_loads
8384
driver_options['DisableBanner'] = options.console.quiet
8485
driver_options['DisableDatabase'] = options.database.disable
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require 'metasploit/framework/data_service/stubs/host_data_service'
2+
require 'metasploit/framework/data_service/stubs/vuln_data_service'
3+
require 'metasploit/framework/data_service/stubs/event_data_service'
4+
require 'metasploit/framework/data_service/stubs/workspace_data_service'
5+
require 'metasploit/framework/data_service/stubs/note_data_service'
6+
require 'metasploit/framework/data_service/stubs/web_data_service'
7+
require 'metasploit/framework/data_service/stubs/service_data_service'
8+
9+
#
10+
# All data service implementations should include this module to ensure proper implementation
11+
#
12+
module Metasploit
13+
module Framework
14+
module DataService
15+
include HostDataService
16+
include EventDataService
17+
include VulnDataService
18+
include WorkspaceDataService
19+
include WebDataService
20+
include NoteDataService
21+
include ServiceDataService
22+
23+
def name
24+
raise 'DataLService#name is not implemented';
25+
end
26+
27+
def active
28+
raise 'DataLService#active is not implemented';
29+
end
30+
end
31+
end
32+
end
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
require 'singleton'
2+
require 'open3'
3+
require 'rex/ui'
4+
require 'rex/logging'
5+
require 'msf/core/db_manager'
6+
require 'metasploit/framework/data_service/remote/http/core'
7+
require 'metasploit/framework/data_service/remote/http/remote_service_endpoint'
8+
require 'metasploit/framework/data_service/proxy/data_proxy_auto_loader'
9+
10+
#
11+
# Holds references to data services (@see Metasploit::Framework::DataService)
12+
# and forwards data to the implementation set as current.
13+
#
14+
module Metasploit
15+
module Framework
16+
module DataService
17+
class DataProxy
18+
include Singleton
19+
include DataProxyAutoLoader
20+
21+
attr_reader :usable
22+
23+
#
24+
# Returns current error state
25+
#
26+
def error
27+
return @error if (@error)
28+
return @data_service.error if @data_service
29+
return "none"
30+
end
31+
32+
#
33+
# Determines if the data service is active
34+
#
35+
def active
36+
if (@data_service)
37+
return @data_service.active
38+
end
39+
40+
return false
41+
end
42+
43+
#
44+
# Initializes the data service to be used - primarily on startup
45+
#
46+
def init(framework, opts)
47+
@mutex.synchronize {
48+
if (@initialized)
49+
return
50+
end
51+
52+
begin
53+
if (opts['DisableDatabase'])
54+
@error = 'disabled'
55+
return
56+
elsif (opts['DatabaseRemoteProcess'])
57+
run_remote_db_process(opts)
58+
else
59+
run_local_db_process(framework, opts)
60+
end
61+
@usable = true
62+
@initialized = true
63+
rescue Exception => e
64+
puts "Unable to initialize a dataservice #{e.message}"
65+
return
66+
end
67+
}
68+
69+
end
70+
71+
#
72+
# Registers a data service with the proxy and immediately
73+
# set as primary if online
74+
#
75+
def register_data_service(data_service, online=false)
76+
validate(data_service)
77+
78+
puts "Registering data service: #{data_service.name}"
79+
data_service_id = @data_service_id += 1
80+
@data_services[data_service_id] = data_service
81+
set_data_service(data_service_id, online)
82+
end
83+
84+
#
85+
# Set the data service to be used
86+
#
87+
def set_data_service(data_service_id, online=false)
88+
data_service = @data_services[data_service_id.to_i]
89+
if (data_service.nil?)
90+
puts "Data service with id: #{data_service_id} does not exist"
91+
return nil
92+
end
93+
94+
if (!online && !data_service.active)
95+
puts "Data service not online: #{data_service.name}, not setting as active"
96+
return nil
97+
end
98+
99+
puts "Setting active data service: #{data_service.name}"
100+
@data_service = data_service
101+
end
102+
103+
#
104+
# Prints out a list of the current data services
105+
#
106+
def print_data_services()
107+
@data_services.each_key {|key|
108+
out = "id: #{key}, description: #{@data_services[key].name}"
109+
if (!@data_service.nil? && @data_services[key].name == @data_service.name)
110+
out += " [active]"
111+
end
112+
puts out #hahaha
113+
}
114+
end
115+
116+
#
117+
# Used to bridge the local db
118+
#
119+
def method_missing(method, *args, &block)
120+
puts "Attempting to delegate method: #{method}"
121+
unless @data_service.nil?
122+
@data_service.send(method, *args, &block)
123+
end
124+
end
125+
126+
#
127+
# Attempt to shutdown the local db process if it exists
128+
#
129+
def exit_called
130+
if @pid
131+
puts 'Killing db process'
132+
begin
133+
Process.kill("TERM", @pid)
134+
rescue Exception => e
135+
puts "Unable to kill db process: #{e.message}"
136+
end
137+
end
138+
end
139+
140+
#########
141+
protected
142+
#########
143+
144+
def get_data_service
145+
raise 'No registered data_service' unless @data_service
146+
return @data_service
147+
end
148+
149+
#######
150+
private
151+
#######
152+
153+
def initialize
154+
@data_services = {}
155+
@data_service_id = 0
156+
@usable = false
157+
@initialized = false
158+
@mutex = Mutex.new()
159+
end
160+
161+
def validate(data_service)
162+
raise "Invalid data_service: #{data_service.class}, not of type Metasploit::Framework::DataService" unless data_service.is_a? (Metasploit::Framework::DataService)
163+
raise 'Cannot register null data service data_service' unless data_service
164+
raise 'Data Service already exists' if data_service_exist?(data_service)
165+
end
166+
167+
def data_service_exist?(data_service)
168+
@data_services.each_value{|value|
169+
if (value.name == data_service.name)
170+
return true
171+
end
172+
}
173+
174+
return false
175+
end
176+
177+
178+
def run_local_db_process(framework, opts)
179+
puts 'Initializing local db process'
180+
db_manager = Msf::DBManager.new(framework)
181+
if (db_manager.usable and not opts['SkipDatabaseInit'])
182+
register_data_service(db_manager, true)
183+
db_manager.init_db(opts)
184+
end
185+
end
186+
187+
def run_remote_db_process(opts)
188+
# started with no signal to prevent ctrl-c from taking out db
189+
db_script = File.join( Msf::Config.install_root, "msfdb -ns")
190+
wait_t = Open3.pipeline_start(db_script)
191+
@pid = wait_t[0].pid
192+
puts "Started process with pid #{@pid}"
193+
194+
endpoint = Metasploit::Framework::DataService::RemoteServiceEndpoint.new('localhost', 8080)
195+
remote_host_data_service = Metasploit::Framework::DataService::RemoteHTTPDataService.new(endpoint)
196+
register_data_service(remote_host_data_service, true)
197+
end
198+
199+
end
200+
end
201+
end
202+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#
2+
# Autoloads specific data proxies
3+
#
4+
module DataProxyAutoLoader
5+
autoload :HostDataProxy, 'metasploit/framework/data_service/proxy/host_data_proxy'
6+
autoload :VulnDataProxy, 'metasploit/framework/data_service/proxy/vuln_data_proxy'
7+
autoload :EventDataProxy, 'metasploit/framework/data_service/proxy/event_data_proxy'
8+
autoload :WorkspaceDataProxy, 'metasploit/framework/data_service/proxy/workspace_data_proxy'
9+
autoload :NoteDataProxy, 'metasploit/framework/data_service/proxy/note_data_proxy'
10+
autoload :WebDataProxy, 'metasploit/framework/data_service/proxy/web_data_proxy'
11+
autoload :WebDataProxy, 'metasploit/framework/data_service/proxy/web_data_proxy'
12+
autoload :ServiceDataProxy, 'metasploit/framework/data_service/proxy/service_data_proxy'
13+
include ServiceDataProxy
14+
include HostDataProxy
15+
include VulnDataProxy
16+
include EventDataProxy
17+
include WorkspaceDataProxy
18+
include NoteDataProxy
19+
include WebDataProxy
20+
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module EventDataProxy
2+
3+
def report_event(opts)
4+
begin
5+
data_service = self.get_data_service()
6+
data_service.report_event(opts)
7+
rescue Exception => e
8+
puts"Call to #{data_service.class}#report_event threw exception: #{e.message}"
9+
end
10+
end
11+
12+
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
module HostDataProxy
2+
3+
def hosts(wspace, non_dead = false, addresses = nil)
4+
begin
5+
data_service = self.get_data_service()
6+
opts = {}
7+
opts[:wspace] = wspace
8+
opts[:non_dead] = non_dead
9+
opts[:addresses] = addresses
10+
data_service.hosts(opts)
11+
rescue Exception => e
12+
puts"Call to #{data_service.class}#hosts threw exception: #{e.message}"
13+
end
14+
end
15+
16+
def find_or_create_host(opts)
17+
puts 'Calling find host'
18+
report_host(opts)
19+
end
20+
21+
def report_host(opts)
22+
return unless valid(opts)
23+
24+
begin
25+
data_service = self.get_data_service()
26+
data_service.report_host(opts)
27+
rescue Exception => e
28+
puts"Call to #{data_service.class}#report_host threw exception: #{e.message}"
29+
opts.each do |key, value| puts "#{key} : #{value}" end
30+
end
31+
end
32+
33+
def report_hosts(hosts)
34+
begin
35+
data_service = self.get_data_service()
36+
data_service.report_hosts(hosts)
37+
rescue Exception => e
38+
puts "Call to #{data_service.class}#report_hosts threw exception: #{e.message}"
39+
end
40+
end
41+
42+
private
43+
44+
def valid(opts)
45+
unless opts[:host]
46+
puts 'Invalid host hash passed, :host is missing'
47+
return false
48+
end
49+
50+
# Sometimes a host setup through a pivot will see the address as "Remote Pipe"
51+
if opts[:host].eql? "Remote Pipe"
52+
puts "Invalid host hash passed, address was of type 'Remote Pipe'"
53+
return false
54+
end
55+
56+
return true
57+
end
58+
59+
end
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module NoteDataProxy
2+
def report_note(opts)
3+
begin
4+
data_service = self.get_data_service()
5+
data_service.report_note(opts)
6+
rescue Exception => e
7+
puts"Call to #{data_service.class}#report_note threw exception: #{e.message}"
8+
end
9+
end
10+
end

0 commit comments

Comments
 (0)