Skip to content

Commit 89a44e8

Browse files
committed
Land #5, Add HTTPS support
2 parents 1c3b43b + 2ffd627 commit 89a44e8

File tree

7 files changed

+163
-43
lines changed

7 files changed

+163
-43
lines changed

lib/metasploit/framework/data_service/proxy/core.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
require 'rex/ui'
33
require 'rex/logging'
44
require 'metasploit/framework/data_service/remote/http/core'
5-
require 'metasploit/framework/data_service/remote/http/remote_service_endpoint'
65
require 'metasploit/framework/data_service/proxy/data_proxy_auto_loader'
76

87
#
@@ -179,7 +178,7 @@ def run_remote_db_process(opts)
179178
@pid = wait_t[0].pid
180179
puts "Started process with pid #{@pid}"
181180

182-
endpoint = Metasploit::Framework::DataService::RemoteServiceEndpoint.new('localhost', 8080)
181+
endpoint = URI.parse('http://localhost:8080')
183182
remote_host_data_service = Metasploit::Framework::DataService::RemoteHTTPDataService.new(endpoint)
184183
register_data_service(remote_host_data_service, true)
185184
end

lib/metasploit/framework/data_service/remote/http/core.rb

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
require 'metasploit/framework/data_service/remote/http/remote_service_endpoint'
21
require 'metasploit/framework/data_service'
32
require 'metasploit/framework/data_service/remote/http/data_service_auto_loader'
43
require 'net/http'
@@ -22,11 +21,12 @@ class RemoteHTTPDataService
2221
DELETE_REQUEST = 'DELETE'
2322

2423
#
25-
# @param endpoint - A RemoteServiceEndpoint. Cannot be nil
24+
# @param [String] endpoint A valid http or https URL. Cannot be nil
2625
#
27-
def initialize(endpoint)
26+
def initialize(endpoint, https_opts = {})
2827
validate_endpoint(endpoint)
29-
@endpoint = endpoint
28+
@endpoint = URI.parse(endpoint)
29+
@https_opts = https_opts
3030
build_client_pool(5)
3131
end
3232

@@ -120,6 +120,11 @@ def make_request(request_type, path, data_hash = nil, query = nil)
120120
puts "HTTP #{request_type} request: #{uri.request_uri} failed with code: #{response.code} message: #{response.body}"
121121
return FailedResponse.new(response)
122122
end
123+
rescue EOFError => e
124+
puts "ERROR: No data was returned from the server."
125+
puts "Backtrace: #{e.message}"
126+
e.backtrace.each { |line| puts "#{line}\n"}
127+
return FailedResponse.new("")
123128
rescue Exception => e
124129
puts "Problem with HTTP #{request_type} request: #{e.message}"
125130
e.backtrace.each { |line| puts "#{line}\n" }
@@ -212,7 +217,6 @@ def initialize(response)
212217

213218
def validate_endpoint(endpoint)
214219
raise 'Endpoint cannot be nil' if endpoint.nil?
215-
raise "Endpoint: #{endpoint.class} not of type RemoteServiceEndpoint" unless endpoint.is_a?(RemoteServiceEndpoint)
216220
end
217221

218222
def append_workspace(data_hash)
@@ -257,11 +261,41 @@ def build_client_pool(size)
257261
@client_pool = Queue.new()
258262
(1..size).each {
259263
http = Net::HTTP.new(@endpoint.host, @endpoint.port)
260-
http.use_ssl = true if @endpoint.use_ssl
264+
if @endpoint.is_a?(URI::HTTPS)
265+
http.use_ssl = true
266+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
267+
unless @https_opts.empty?
268+
if @https_opts[:skip_verify]
269+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
270+
else
271+
# https://stackoverflow.com/questions/22093042/implementing-https-certificate-pubkey-pinning-with-ruby
272+
user_passed_cert = OpenSSL::X509::Certificate.new(File.read(@https_opts[:cert]))
273+
274+
http.verify_callback = lambda do |preverify_ok, cert_store|
275+
server_cert = cert_store.chain[0]
276+
return true unless server_cert.to_der == cert_store.current_cert.to_der
277+
same_public_key?(server_cert, user_passed_cert)
278+
end
279+
end
280+
end
281+
end
261282
@client_pool << http
262283
}
263284
end
264285

286+
# Tells us whether the private keys on the passed certificates match
287+
# and use the same algo
288+
def same_public_key?(ref_cert, actual_cert)
289+
pkr, pka = ref_cert.public_key, actual_cert.public_key
290+
291+
# First check if the public keys use the same crypto...
292+
return false unless pkr.class == pka.class
293+
# ...and then - that they have the same contents
294+
return false unless pkr.to_pem == pka.to_pem
295+
296+
true
297+
end
298+
265299
def try_sound_effect()
266300
sound_file = ::File.join(Msf::Config.data_directory, "sounds", "Goliath_Online_Sound_Effect.wav")
267301
Rex::Compat.play_sound(sound_file)

lib/metasploit/framework/data_service/remote/http/remote_service_endpoint.rb

Lines changed: 0 additions & 26 deletions
This file was deleted.

lib/metasploit/framework/data_service/remote/msf_red/msf_red_service.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
require 'metasploit/framework/data_service'
22
require 'metasploit/framework/data_service/remote/http/core'
3-
require 'metasploit/framework/data_service/remote/http/remote_service_endpoint'
43

54
class MSFRedService
65
JOB_CHECK_INTERVAL_SEC = 5
@@ -42,8 +41,8 @@ def load_job_handlers
4241
end
4342

4443
def inject_data_service
45-
remote_service_endpoint = Metasploit::Framework::DataService::RemoteServiceEndpoint.new(CONSOLE_SERVICE_HOST_NAME, CONSOLE_SERVICE_PORT)
46-
remote_data_service = Metasploit::Framework::DataService::RemoteHTTPDataService.new(remote_service_endpoint)
44+
endpoint = URI.parse("http://#{CONSOLE_SERVICE_HOST_NAME}:#{CONSOLE_SERVICE_PORT}")
45+
remote_data_service = Metasploit::Framework::DataService::RemoteHTTPDataService.new(endpoint)
4746
remote_data_service.set_header(SESSION_KEY_VALUE, @session_key)
4847
data_service_manager = Metasploit::Framework::DataService::DataProxy.instance
4948
data_service_manager.register_data_service(remote_data_service)

lib/msf/core/db_manager/http/http_db_manager_service.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ def start(opts)
1616

1717
require_environment!(parsed_options)
1818

19+
if opts[:ssl]
20+
ssl_opts = {}
21+
ssl_opts[:private_key_file] = opts[:ssl_key]
22+
ssl_opts[:cert_chain_file] = opts[:ssl_cert]
23+
ssl_opts[:verify_peer] = false
24+
opts[:ssl] = true
25+
opts[:ssl_opts] = ssl_opts
26+
end
27+
1928
init_db
2029
start_http_server(opts)
2130
end
@@ -33,6 +42,11 @@ def start_http_server(opts)
3342
}
3443
}
3544

45+
if opts[:ssl] && opts[:ssl] = true
46+
puts "Starting in HTTPS mode"
47+
server.ssl = true
48+
server.ssl_options = opts[:ssl_opts]
49+
end
3650
server.threaded = true
3751
end
3852
end

lib/msf/ui/console/command_dispatcher/db.rb

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
require 'msf/core/db_export'
77
require 'metasploit/framework/data_service'
88
require 'metasploit/framework/data_service/remote/http/core'
9-
require 'metasploit/framework/data_service/remote/http/remote_service_endpoint'
109

1110
module Msf
1211
module Ui
@@ -98,20 +97,49 @@ def cmd_list_data_services()
9897
end
9998

10099
def cmd_add_data_service(*args)
100+
protocol = "http"
101+
port = 80
102+
https_opts = {}
101103
while (arg = args.shift)
102104
case arg
103-
when '-h'
104-
host = args.shift
105+
when '-h', '--help'
106+
cmd_add_data_service_help
107+
return
105108
when '-p'
106109
port = args.shift
110+
when '-s', '--ssl'
111+
protocol = "https"
112+
when '-c', '--cert'
113+
https_opts[:cert] = args.shift
114+
when '--skip-verify'
115+
https_opts[:skip_verify] = true
116+
else
117+
host = arg
107118
end
108119
end
109120

110-
remote_service_endpoint = Metasploit::Framework::DataService::RemoteServiceEndpoint.new(host, port)
111-
remote_data_service = Metasploit::Framework::DataService::RemoteHTTPDataService.new(remote_service_endpoint)
121+
if host.nil? || port.nil?
122+
print_error "Host and port are required."
123+
return
124+
end
125+
126+
endpoint = "#{protocol}://#{host}:#{port}"
127+
remote_data_service = Metasploit::Framework::DataService::RemoteHTTPDataService.new(endpoint, https_opts)
112128
framework.db.register_data_service(remote_data_service)
113129
end
114130

131+
def cmd_add_data_service_help
132+
print_line "Usage: add_data_service [ options ] [ Remote Address]"
133+
print_line
134+
print_line "OPTIONS:"
135+
print_line " -h, --help Show this help information."
136+
print_line " -p <port> The port the data service is listening on. Default is 80."
137+
print_line " -s, --ssl Enable SSL. Required for HTTPS data services."
138+
print_line " -c, --cert Certificate file matching the server's certificate. Needed when using self-signed SSL cert."
139+
print_line " --skip-verify Skip validating authenticity of server's certificate. NOT RECOMMENDED."
140+
print_line
141+
end
142+
115143
def cmd_test_data_service_host(*args)
116144
host = {}
117145
while (arg = args.shift)

msfdb

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,76 @@
77
require 'pathname'
88
require Pathname.new(__FILE__).realpath.expand_path.parent.join('config', 'boot')
99
require 'msf/core/db_manager/http/http_db_manager_service'
10-
HttpDBManagerService.new().start(:Port => '8080', :Host => '0.0.0.0')
10+
require 'optparse'
11+
12+
class HelpError < StandardError; end
13+
14+
class SwitchError < StandardError
15+
def initialize(msg="Missing required switch.")
16+
super(msg)
17+
end
18+
end
19+
20+
def parse_args(args)
21+
opts = {}
22+
opt = OptionParser.new
23+
banner = "msfdb - A remote database process for Metasploit Framework.\n"
24+
banner << "Usage: #{$0} [options] <var=val>"
25+
opt.banner = banner
26+
opt.separator('')
27+
opt.separator('Options:')
28+
29+
# Defaults:
30+
opts[:interface] = '0.0.0.0'
31+
opts[:port] = 8080
32+
opts[:ssl] = false
33+
opts[:ssl_cert] = nil
34+
opts[:ssl_key] = nil
35+
36+
opt.on('-i', '--interface <interface>', String, 'Interface to listen on') do |p|
37+
opts[:interface] = p
38+
end
39+
40+
opt.on('-p', '--port <port number>', Integer, 'Port to listen on') do |p|
41+
opts[:port] = p
42+
end
43+
44+
opt.on('-s', '--ssl', 'Enable SSL on the server') do |p|
45+
opts[:ssl] = true
46+
end
47+
48+
opt.on('-c', '--cert <path/to/cert.pem>', String, 'Path to SSL Certificate file') do |p|
49+
opts[:ssl_cert] = p
50+
end
51+
52+
opt.on('-k', '--key <path/to/key.pem>', String, 'Path to SSL Key file') do |p|
53+
opts[:ssl_key] = p
54+
end
55+
56+
opt.on_tail('-h', '--help', 'Show this message') do
57+
raise HelpError, "#{opt}"
58+
end
59+
60+
begin
61+
opt.parse!(args)
62+
rescue OptionParser::InvalidOption => e
63+
raise UsageError, "Invalid option\n#{opt}"
64+
rescue OptionParser::MissingArgument => e
65+
raise UsageError, "Missing required argument for option\n#{opt}"
66+
end
67+
68+
opts
69+
end
70+
71+
begin
72+
opts = parse_args(ARGV)
73+
raise SwitchError.new("certificate file and key file must be specified when using -s") if opts[:ssl] && (opts[:ssl_key].nil? || opts[:ssl_cert].nil?)
74+
HttpDBManagerService.new.start(:Port => opts[:port],
75+
:Host => opts[:interface],
76+
:ssl => opts[:ssl],
77+
:ssl_cert => opts[:ssl_cert],
78+
:ssl_key => opts[:ssl_key])
79+
rescue HelpError => e
80+
$stderr.puts e.message
81+
end
82+

0 commit comments

Comments
 (0)