Mirror of metasploit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

msfdb 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  1. #!/usr/bin/env ruby
  2. require 'fileutils'
  3. require 'json'
  4. require 'net/http'
  5. require 'net/https'
  6. require 'open3'
  7. require 'optparse'
  8. require 'rex/socket'
  9. require 'rex/text'
  10. require 'sysrandom/securerandom'
  11. require 'uri'
  12. require 'yaml'
  13. msfbase = __FILE__
  14. while File.symlink?(msfbase)
  15. msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
  16. end
  17. $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib')))
  18. $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
  19. require 'msf/util/helper'
  20. @script_name = File.basename(__FILE__)
  21. @framework = File.expand_path(File.dirname(__FILE__))
  22. @localconf = "#{ENV['HOME']}/.msf4"
  23. @db = "#{@localconf}/db"
  24. @db_conf = "#{@localconf}/database.yml"
  25. @ws_tag = 'msf-ws'
  26. @ws_conf = "#{@localconf}/#{@ws_tag}-config.ru"
  27. @ws_ssl_key_default = "#{@localconf}/#{@ws_tag}-key.pem"
  28. @ws_ssl_cert_default = "#{@localconf}/#{@ws_tag}-cert.pem"
  29. @ws_log = "#{@localconf}/logs/#{@ws_tag}.log"
  30. @ws_pid = "#{@localconf}/#{@ws_tag}.pid"
  31. @current_user = ENV['LOGNAME'] || ENV['USERNAME'] || ENV['USER']
  32. @msf_ws_user = (@current_user || "msfadmin").to_s.strip
  33. @ws_generated_ssl = false
  34. @ws_api_token = nil
  35. @components = %w(database webservice)
  36. @environments = %w(production development)
  37. @options = {
  38. component: :all,
  39. debug: false,
  40. msf_db_name: 'msf',
  41. msf_db_user: 'msf',
  42. msftest_db_name: 'msftest',
  43. msftest_db_user: 'msftest',
  44. db_port: 5433,
  45. db_pool: 200,
  46. address: 'localhost',
  47. port: 8080,
  48. ssl: true,
  49. ssl_cert: @ws_ssl_cert_default,
  50. ssl_key: @ws_ssl_key_default,
  51. ssl_disable_verify: true,
  52. ws_env: ENV['RACK_ENV'] || 'production',
  53. retry_max: 10,
  54. retry_delay: 5.0,
  55. ws_user: nil
  56. }
  57. def run_cmd(cmd, input: nil, env: {})
  58. exitstatus = 0
  59. err = out = ""
  60. puts "run_cmd: cmd=#{cmd}, input=#{input}, env=#{env}" if @options[:debug]
  61. Open3.popen3(env, cmd) do |stdin, stdout, stderr, wait_thr|
  62. stdin.puts(input) if input
  63. if @options[:debug]
  64. err = stderr.read
  65. out = stdout.read
  66. end
  67. exitstatus = wait_thr.value.exitstatus
  68. end
  69. if exitstatus != 0
  70. if @options[:debug]
  71. puts "'#{cmd}' returned #{exitstatus}"
  72. puts out
  73. puts err
  74. end
  75. end
  76. exitstatus
  77. end
  78. def run_psql(cmd, db_name: 'postgres')
  79. if @options[:debug]
  80. puts "psql -p #{@options[:db_port]} -c \"#{cmd};\" #{db_name}"
  81. end
  82. run_cmd("psql -p #{@options[:db_port]} -c \"#{cmd};\" #{db_name}")
  83. end
  84. def pw_gen
  85. SecureRandom.base64(32)
  86. end
  87. def tail(file)
  88. begin
  89. File.readlines(file).last.to_s.strip
  90. rescue
  91. nil
  92. end
  93. end
  94. def status_db
  95. update_db_port
  96. if Dir.exist?(@db)
  97. if run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} status") == 0
  98. puts "Database started at #{@db}"
  99. else
  100. puts "Database is not running at #{@db}"
  101. end
  102. else
  103. puts "No database found at #{@db}"
  104. end
  105. end
  106. def started_db
  107. if run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} status") == 0
  108. puts "Database already started at #{@db}"
  109. return true
  110. end
  111. print "Starting database at #{@db}..."
  112. run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} -l #{@db}/log start")
  113. sleep(2)
  114. if run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} status") != 0
  115. puts 'failed'
  116. false
  117. else
  118. puts 'success'
  119. true
  120. end
  121. end
  122. def start_db
  123. if !Dir.exist?(@db)
  124. puts "No database found at #{@db}, not starting"
  125. return
  126. end
  127. update_db_port
  128. while !started_db
  129. last_log = tail("#{@db}/log")
  130. puts last_log
  131. fixed = false
  132. if last_log =~ /not compatible/
  133. puts 'Please attempt to upgrade the database manually using pg_upgrade.'
  134. end
  135. if !fixed
  136. if ask_yn('Your database may be corrupt, would you like to reinitialize it?')
  137. fixed = reinit_db
  138. end
  139. end
  140. if !fixed
  141. if !ask_yn('Database not started, try again?')
  142. return
  143. end
  144. end
  145. end
  146. end
  147. def stop_db
  148. update_db_port
  149. if run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} status") == 0
  150. puts "Stopping database at #{@db}"
  151. run_cmd("pg_ctl -o \"-p #{@options[:db_port]}\" -D #{@db} stop")
  152. else
  153. puts "Database is no longer running at #{@db}"
  154. end
  155. end
  156. def restart_db
  157. stop_db
  158. start_db
  159. end
  160. def create_db
  161. puts "Creating database at #{@db}"
  162. Dir.mkdir(@db)
  163. run_cmd("initdb --auth-host=trust --auth-local=trust -E UTF8 #{@db}")
  164. File.open("#{@db}/postgresql.conf", 'a') do |f|
  165. f.puts "port = #{@options[:db_port]}"
  166. end
  167. end
  168. def init_db
  169. if Dir.exist?(@db)
  170. puts "Found a database at #{@db}, checking to see if it is started"
  171. start_db
  172. return
  173. end
  174. if File.exist?(@db_conf) &&
  175. !ask_yn("Found database config at #{@db_conf}, do you want to overwrite it?")
  176. if !load_db_config
  177. puts "Failed to load existing database config. Please reinit and overwrite the file."
  178. return
  179. end
  180. else
  181. write_db_config
  182. end
  183. create_db
  184. start_db
  185. puts 'Creating database users'
  186. run_psql("create user #{@options[:msf_db_user]} with password '#{@msf_pass}'")
  187. run_psql("create user #{@options[:msftest_db_user]} with password '#{@msftest_pass}'")
  188. run_psql("alter role #{@options[:msf_db_user]} createdb")
  189. run_psql("alter role #{@options[:msftest_db_user]} createdb")
  190. run_psql("alter role #{@options[:msf_db_user]} with password '#{@msf_pass}'")
  191. run_psql("alter role #{@options[:msftest_db_user]} with password '#{@msftest_pass}'")
  192. run_cmd("createdb -p #{@options[:db_port]} -O #{@options[:msf_db_user]} -h 127.0.0.1 -U #{@options[:msf_db_user]} -E UTF-8 -T template0 #{@options[:msf_db_name]}",
  193. input: "#{@msf_pass}\n#{@msf_pass}\n")
  194. run_cmd("createdb -p #{@options[:db_port]} -O #{@options[:msftest_db_user]} -h 127.0.0.1 -U #{@options[:msftest_db_user]} -E UTF-8 -T template0 #{@options[:msftest_db_name]}",
  195. input: "#{@msftest_pass}\n#{@msftest_pass}\n")
  196. write_db_client_auth_config
  197. restart_db
  198. puts 'Creating initial database schema'
  199. Dir.chdir(@framework) do
  200. run_cmd('bundle exec rake db:migrate')
  201. end
  202. end
  203. def load_db_config
  204. if File.file?(@db_conf)
  205. config = YAML.load(File.read(@db_conf))
  206. production = config['production']
  207. if production.nil?
  208. puts "No production section found in database config #{@db_conf}."
  209. return false
  210. end
  211. test = config['test']
  212. if test.nil?
  213. puts "No test section found in database config #{@db_conf}."
  214. return false
  215. end
  216. # get values for development and production
  217. @options[:msf_db_name] = production['database']
  218. @options[:msf_db_user] = production['username']
  219. @msf_pass = production['password']
  220. @options[:db_port] = production['port']
  221. @options[:db_pool] = production['pool']
  222. # get values for test
  223. @options[:msftest_db_name] = test['database']
  224. @options[:msftest_db_user] = test['username']
  225. @msftest_pass = test['password']
  226. return true
  227. end
  228. return false
  229. end
  230. def write_db_config
  231. # Generate new database passwords if not already assigned
  232. @msf_pass ||= pw_gen
  233. @msftest_pass ||= pw_gen
  234. # Write a default database config file
  235. Dir.mkdir(@localconf) unless File.directory?(@localconf)
  236. File.open(@db_conf, 'w') do |f|
  237. f.puts <<~EOF
  238. development: &pgsql
  239. adapter: postgresql
  240. database: #{@options[:msf_db_name]}
  241. username: #{@options[:msf_db_user]}
  242. password: #{@msf_pass}
  243. host: 127.0.0.1
  244. port: #{@options[:db_port]}
  245. pool: #{@options[:db_pool]}
  246. production: &production
  247. <<: *pgsql
  248. test:
  249. <<: *pgsql
  250. database: #{@options[:msftest_db_name]}
  251. username: #{@options[:msftest_db_user]}
  252. password: #{@msftest_pass}
  253. EOF
  254. end
  255. File.chmod(0640, @db_conf)
  256. end
  257. def write_db_client_auth_config
  258. client_auth_config = "#{@db}/pg_hba.conf"
  259. puts "Writing client authentication configuration file #{client_auth_config}"
  260. File.open(client_auth_config, 'w') do |f|
  261. f.puts "host \"#{@options[:msf_db_name]}\" \"#{@options[:msf_db_user]}\" 127.0.0.1/32 md5"
  262. f.puts "host \"#{@options[:msftest_db_name]}\" \"#{@options[:msftest_db_user]}\" 127.0.0.1/32 md5"
  263. f.puts "host \"postgres\" \"#{@options[:msftest_db_user]}\" 127.0.0.1/32 md5"
  264. f.puts "host \"template1\" all 127.0.0.1/32 trust"
  265. if Gem.win_platform?
  266. f.puts "host all all 127.0.0.1/32 trust"
  267. f.puts "host all all ::1/128 trust"
  268. else
  269. f.puts "local all all trust"
  270. end
  271. end
  272. end
  273. def update_db_port
  274. if File.file?(@db_conf)
  275. config = YAML.load(File.read(@db_conf))
  276. if config["production"] && config["production"]["port"]
  277. port = config["production"]["port"]
  278. if port != @options[:db_port]
  279. puts "Using database port #{port} found in #{@db_conf}"
  280. @options[:db_port] = port
  281. end
  282. end
  283. end
  284. end
  285. def ask_yn(question)
  286. loop do
  287. print "#{question}: "
  288. yn = STDIN.gets
  289. case yn
  290. when /^[Yy]/
  291. return true
  292. when /^[Nn]/
  293. return false
  294. else
  295. puts 'Please answer yes or no.'
  296. end
  297. end
  298. end
  299. def ask_value(question, default_value)
  300. print "#{question}[#{default_value}]: "
  301. input = STDIN.gets.strip
  302. if input.nil? || input.empty?
  303. return default_value
  304. else
  305. return input
  306. end
  307. end
  308. def delete_db
  309. if Dir.exist?(@db)
  310. stop_db
  311. if ask_yn("Delete all data at #{@db}?")
  312. puts "Deleting all data at #{@db}"
  313. FileUtils.rm_rf(@db)
  314. end
  315. if File.file?(@db_conf) && ask_yn("Delete database configuration at #{@db_conf}?")
  316. File.delete(@db_conf)
  317. end
  318. else
  319. puts "No data at #{@db}, doing nothing"
  320. end
  321. end
  322. def reinit_db
  323. delete_db
  324. init_db
  325. end
  326. def status_web_service
  327. if File.file?(@ws_pid)
  328. ws_pid = tail(@ws_pid)
  329. if ws_pid.nil? || !process_active?(ws_pid.to_i)
  330. puts "MSF web service is not running: PID file found at #{@ws_pid}, but no active process running as PID #{ws_pid}"
  331. else
  332. puts "MSF web service is running as PID #{ws_pid}"
  333. end
  334. else
  335. puts "MSF web service is not running: no PID file found at #{@ws_pid}"
  336. end
  337. end
  338. def init_web_service
  339. if File.file?(@ws_conf)
  340. puts "Found web service config at #{@ws_conf}, checking to see if it is started"
  341. start_web_service(expect_auth: true)
  342. return
  343. end
  344. if @options[:ws_user].nil?
  345. @msf_ws_user = ask_value('Initial MSF web service account username?', @msf_ws_user)
  346. else
  347. @msf_ws_user = @options[:ws_user]
  348. end
  349. # Write a default Rack config file for the web service
  350. Dir.mkdir(@localconf) unless File.directory?(@localconf)
  351. # TODO: free the REST API from all of these requirements
  352. File.open(@ws_conf, 'w') do |f|
  353. f.puts <<~EOF
  354. # #{File.basename(@ws_conf)}
  355. # created on: #{Time.now.utc}
  356. @framework_path = '#{@framework}'
  357. $LOAD_PATH << @framework_path unless $LOAD_PATH.include?(@framework_path)
  358. require File.expand_path('./config/boot', @framework_path)
  359. require 'metasploit/framework/parsed_options/remote_db'
  360. require 'msf/core/web_services/metasploit_api_app'
  361. def require_environment!(parsed_options)
  362. # RAILS_ENV must be set before requiring 'config/application.rb'
  363. parsed_options.environment!
  364. ARGV.replace(parsed_options.positional)
  365. # allow other Rails::Applications to use this command
  366. if !defined?(Rails) || Rails.application.nil?
  367. # @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/commands.rb#L39-L40
  368. require File.expand_path('./config/application', @framework_path)
  369. end
  370. # have to configure before requiring environment because
  371. # config/environment.rb calls initialize! and the initializers will use
  372. # the configuration from the parsed options.
  373. parsed_options.configure(Rails.application)
  374. Rails.application.require_environment!
  375. end
  376. parsed_options = Metasploit::Framework::ParsedOptions::RemoteDB.new
  377. require_environment!(parsed_options)
  378. run MetasploitApiApp
  379. EOF
  380. end
  381. File.chmod(0640, @ws_conf)
  382. if @options[:ssl] && ((!File.file?(@options[:ssl_key]) || !File.file?(@options[:ssl_cert])) ||
  383. (@options[:ssl_key] == @ws_ssl_key_default && @options[:ssl_cert] == @ws_ssl_cert_default))
  384. generate_web_service_ssl(key: @options[:ssl_key], cert: @options[:ssl_cert])
  385. end
  386. if start_web_service(expect_auth: false)
  387. if add_web_service_workspace && add_web_service_user
  388. output_web_service_information
  389. else
  390. puts 'Failed to complete MSF web service configuration, please reinitialize.'
  391. stop_web_service
  392. end
  393. end
  394. end
  395. def start_web_service(expect_auth: true)
  396. unless File.file?(@ws_conf)
  397. puts "No MSF web service configuration found at #{@ws_conf}, not starting"
  398. return false
  399. end
  400. # check if MSF web service is already started
  401. if File.file?(@ws_pid)
  402. ws_pid = tail(@ws_pid)
  403. if ws_pid.nil? || !process_active?(ws_pid.to_i)
  404. puts "MSF web service PID file found, but no active process running as PID #{ws_pid}"
  405. puts "Deleting MSF web service PID file #{@ws_pid}"
  406. File.delete(@ws_pid)
  407. else
  408. puts "MSF web service is already running as PID #{ws_pid}"
  409. return false
  410. end
  411. end
  412. # daemonize MSF web service
  413. puts 'Attempting to start MSF web service...'
  414. if run_cmd("#{thin_cmd} start") == 0
  415. # wait until web service is online
  416. retry_count = 0
  417. response_data = web_service_online_check(expect_auth: expect_auth)
  418. is_online = response_data[:state] != :offline
  419. while !is_online && retry_count < @options[:retry_max]
  420. retry_count += 1
  421. if @options[:debug]
  422. puts "MSF web service doesn't appear to be online. Sleeping #{@options[:retry_delay]}s until check #{retry_count}/#{@options[:retry_max]}"
  423. end
  424. sleep(@options[:retry_delay])
  425. response_data = web_service_online_check(expect_auth: expect_auth)
  426. is_online = response_data[:state] != :offline
  427. end
  428. if response_data[:state] == :online
  429. puts 'MSF web service started and online'
  430. return true
  431. elsif response_data[:state] == :error
  432. puts 'MSF web service appears to be started, but may not operate as expected.'
  433. puts "#{response_data[:message]}"
  434. else
  435. puts 'MSF web service does not appear to be started.'
  436. end
  437. puts "Please see #{@ws_log} for additional details."
  438. return false
  439. else
  440. puts 'Failed to start MSF web service'
  441. return false
  442. end
  443. end
  444. def stop_web_service
  445. ws_pid = tail(@ws_pid)
  446. if ws_pid.nil? || !process_active?(ws_pid.to_i)
  447. puts 'MSF web service is no longer running'
  448. if File.file?(@ws_pid)
  449. puts "Deleting MSF web service PID file #{@ws_pid}"
  450. File.delete(@ws_pid)
  451. end
  452. else
  453. puts "Stopping MSF web service PID #{ws_pid}"
  454. run_cmd("#{thin_cmd} stop")
  455. end
  456. end
  457. def restart_web_service
  458. stop_web_service
  459. start_web_service
  460. end
  461. def delete_web_service
  462. stop_web_service
  463. if File.file?(@ws_conf) && ask_yn("Delete MSF web service configuration at #{@ws_conf}?")
  464. File.delete(@ws_conf)
  465. end
  466. end
  467. def reinit_web_service
  468. delete_web_service
  469. init_web_service
  470. end
  471. def generate_web_service_ssl(key:, cert:)
  472. @ws_generated_ssl = true
  473. if (File.file?(key) || File.file?(cert)) &&
  474. !ask_yn("Either MSF web service SSL key #{key} or certificate #{cert} already exist, overwrite both?")
  475. return
  476. end
  477. puts 'Generating SSL key and certificate for MSF web service'
  478. # @ssl_cert = Rex::Socket::SslTcpServer.ssl_generate_certificate
  479. @ssl_key, @ssl_cert, @ssl_extra_chain_cert = Rex::Socket::Ssl.ssl_generate_certificate
  480. # write PEM format key and certificate
  481. mode = 'wb'
  482. mode_int = 0600
  483. File.open(key, mode) { |f| f.write(@ssl_key.to_pem) }
  484. File.chmod(mode_int, key)
  485. File.open(cert, mode) { |f| f.write(@ssl_cert.to_pem) }
  486. File.chmod(mode_int, cert)
  487. end
  488. def web_service_online_check(expect_auth:)
  489. msf_version_uri = get_web_service_uri(path: '/api/v1/msf/version')
  490. response_data = http_request(uri: msf_version_uri, method: :get,
  491. skip_verify: skip_ssl_verify?, cert: get_ssl_cert)
  492. if !response_data[:exception].nil? && response_data[:exception].is_a?(Errno::ECONNREFUSED)
  493. response_data[:state] = :offline
  494. elsif !response_data[:exception].nil? && response_data[:exception].is_a?(OpenSSL::OpenSSLError)
  495. response_data[:state] = :error
  496. response_data[:message] = 'Detected an SSL issue. Please set the same options used to initialize the web service or reinitialize.'
  497. elsif !response_data[:response].nil? && response_data[:response].dig(:error, :code) == 401
  498. if expect_auth
  499. response_data[:state] = :online
  500. else
  501. response_data[:state] = :error
  502. response_data[:message] = 'MSF web service expects authentication. If you wish to reinitialize the web service account you will need to reinitialize the database.'
  503. end
  504. elsif !response_data[:response].nil? && !response_data[:response].dig(:data, :metasploit_version).nil?
  505. response_data[:state] = :online
  506. else
  507. response_data[:state] = :error
  508. end
  509. puts "web_service_online: expect_auth=#{expect_auth}, response_msg=#{response_data}" if @options[:debug]
  510. response_data
  511. end
  512. def add_web_service_workspace(name: 'default')
  513. # Send request to create new workspace
  514. workspace_data = { name: name }
  515. workspaces_uri = get_web_service_uri(path: '/api/v1/workspaces')
  516. response_data = http_request(uri: workspaces_uri, data: workspace_data, method: :post,
  517. skip_verify: skip_ssl_verify?, cert: get_ssl_cert)
  518. response = response_data[:response]
  519. puts "add_web_service_workspace: add workspace response=#{response}" if @options[:debug]
  520. if response.nil? || response.dig(:data, :name) != name
  521. puts "Error creating MSF web service workspace '#{name}'"
  522. return false
  523. end
  524. return true
  525. end
  526. def add_web_service_user
  527. puts "Creating MSF web service user #{@msf_ws_user}"
  528. # Generate new web service user password
  529. msf_ws_pass = pw_gen
  530. cred_data = { username: @msf_ws_user, password: msf_ws_pass }
  531. # Send request to create new admin user
  532. user_data = cred_data.merge({ admin: true })
  533. user_uri = get_web_service_uri(path: '/api/v1/users')
  534. response_data = http_request(uri: user_uri, data: user_data, method: :post,
  535. skip_verify: skip_ssl_verify?, cert: get_ssl_cert)
  536. response = response_data[:response]
  537. puts "add_web_service_user: create user response=#{response}" if @options[:debug]
  538. if response.nil? || response.dig(:data, :username) != @msf_ws_user
  539. puts "Error creating MSF web service user #{@msf_ws_user}"
  540. return false
  541. end
  542. puts "\nMSF web service username: #{@msf_ws_user}"
  543. puts "MSF web service password: #{msf_ws_pass}"
  544. # Send request to create new API token for the user
  545. generate_token_uri = get_web_service_uri(path: '/api/v1/auth/generate-token')
  546. response_data = http_request(uri: generate_token_uri, query: cred_data, method: :get,
  547. skip_verify: skip_ssl_verify?, cert: get_ssl_cert)
  548. response = response_data[:response]
  549. puts "add_web_service_user: generate token response=#{response}" if @options[:debug]
  550. if response.nil? || (@ws_api_token = response.dig(:data, :token)).nil?
  551. puts 'Error creating MSF web service user API token'
  552. return false
  553. end
  554. puts "MSF web service user API token: #{@ws_api_token}"
  555. puts 'Please store these credentials securely.'
  556. return true
  557. end
  558. def output_web_service_information
  559. puts ''
  560. puts 'MSF web service configuration complete'
  561. puts 'Connect to the data service in msfconsole using the command:'
  562. puts "#{get_db_connect_command}"
  563. puts ''
  564. puts 'The username and password are credentials for the API account:'
  565. puts "#{get_web_service_uri(path: '/api/v1/auth/account')}"
  566. puts ''
  567. persist_data_service
  568. end
  569. def persist_data_service
  570. if ask_yn('Add data service connection to local msfconsole and persist as default?')
  571. data_service_name = "local-#{@options[:ssl] ? 'https' : 'http'}-data-service"
  572. data_service_name = ask_value('Data service connection name?', data_service_name)
  573. # execute msfconsole commands to add and persist the data service connection
  574. connect_cmd = get_db_connect_command(name: data_service_name)
  575. cmd = "msfconsole -qx \"#{connect_cmd}; db_save; exit\""
  576. if run_cmd(cmd) != 0
  577. # attempt to execute msfconsole in the current working directory
  578. if run_cmd(cmd, env: {'PATH' => ".:#{ENV["PATH"]}"}) != 0
  579. puts 'Failed to run msfconsole and persist the data service connection'
  580. end
  581. end
  582. end
  583. end
  584. def get_db_connect_command(name: nil)
  585. # build db_connect command based on install options
  586. connect_cmd = "db_connect"
  587. connect_cmd << " --name #{name}" unless name.nil?
  588. connect_cmd << " --token #{@ws_api_token}"
  589. connect_cmd << " --cert #{@options[:ssl_cert]}" if @options[:ssl]
  590. connect_cmd << " --skip-verify" if skip_ssl_verify?
  591. connect_cmd << " #{get_web_service_uri}"
  592. connect_cmd
  593. end
  594. def get_web_service_uri(path: nil)
  595. uri_class = @options[:ssl] ? URI::HTTPS : URI::HTTP
  596. uri_class.build({host: get_web_service_host, port: @options[:port], path: path})
  597. end
  598. def get_web_service_host
  599. # user specified any address INADDR_ANY (0.0.0.0), return a routable address
  600. @options[:address] == '0.0.0.0' ? 'localhost' : @options[:address]
  601. end
  602. def skip_ssl_verify?
  603. @ws_generated_ssl || @options[:ssl_disable_verify]
  604. end
  605. def get_ssl_cert
  606. @options[:ssl] ? @options[:ssl_cert] : nil
  607. end
  608. def thin_cmd
  609. server_opts = "--rackup #{@ws_conf} --address #{@options[:address]} --port #{@options[:port]}"
  610. ssl_opts = @options[:ssl] ? "--ssl --ssl-key-file #{@options[:ssl_key]} --ssl-cert-file #{@options[:ssl_cert]}" : ''
  611. ssl_opts << ' --ssl-disable-verify' if skip_ssl_verify?
  612. adapter_opts = "--environment #{@options[:ws_env]}"
  613. daemon_opts = "--daemonize --log #{@ws_log} --pid #{@ws_pid} --tag #{@ws_tag}"
  614. all_opts = [server_opts, ssl_opts, adapter_opts, daemon_opts].reject(&:empty?).join(' ')
  615. "thin #{all_opts}"
  616. end
  617. def process_active?(pid)
  618. begin
  619. Process.kill(0, pid)
  620. true
  621. rescue Errno::ESRCH
  622. false
  623. end
  624. end
  625. def http_request(uri:, query: nil, data: nil, method: :get, headers: nil, skip_verify: false, cert: nil)
  626. all_headers = { 'User-Agent': @script_name }
  627. all_headers.merge!(headers) unless headers.nil?
  628. query_str = (!query.nil? && !query.empty?) ? URI.encode_www_form(query.compact) : nil
  629. uri.query = query_str
  630. http = Net::HTTP.new(uri.host, uri.port)
  631. if uri.is_a?(URI::HTTPS)
  632. http.use_ssl = true
  633. if skip_verify
  634. http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  635. else
  636. # https://stackoverflow.com/questions/22093042/implementing-https-certificate-pubkey-pinning-with-ruby
  637. http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  638. user_passed_cert = OpenSSL::X509::Certificate.new(File.read(cert))
  639. http.verify_callback = lambda do |preverify_ok, cert_store|
  640. server_cert = cert_store.chain[0]
  641. return true unless server_cert.to_der == cert_store.current_cert.to_der
  642. same_public_key?(server_cert, user_passed_cert)
  643. end
  644. end
  645. end
  646. begin
  647. response_data = { response: nil }
  648. case method
  649. when :get
  650. request = Net::HTTP::Get.new(uri.request_uri, initheader=all_headers)
  651. when :post
  652. request = Net::HTTP::Post.new(uri.request_uri, initheader=all_headers)
  653. else
  654. raise Exception, "Request method #{method} is not handled"
  655. end
  656. request.content_type = 'application/json'
  657. unless data.nil?
  658. json_body = data.to_json
  659. request.body = json_body
  660. end
  661. response = http.request(request)
  662. unless response.body.nil? || response.body.empty?
  663. response_data[:response] = JSON.parse(response.body, symbolize_names: true)
  664. end
  665. rescue => e
  666. response_data[:exception] = e
  667. puts "Problem with HTTP #{method} request #{uri.request_uri}, message: #{e.message}" if @options[:debug]
  668. end
  669. response_data
  670. end
  671. # Tells us whether the private keys on the passed certificates match
  672. # and use the same algo
  673. def same_public_key?(ref_cert, actual_cert)
  674. pkr, pka = ref_cert.public_key, actual_cert.public_key
  675. # First check if the public keys use the same crypto...
  676. return false unless pkr.class == pka.class
  677. # ...and then - that they have the same contents
  678. return false unless pkr.to_pem == pka.to_pem
  679. true
  680. end
  681. def parse_args(args)
  682. subtext = <<~USAGE
  683. Commands:
  684. init initialize the component
  685. reinit delete and reinitialize the component
  686. delete delete and stop the component
  687. status check component status
  688. start start the component
  689. stop stop the component
  690. restart restart the component
  691. USAGE
  692. parser = OptionParser.new do |opts|
  693. opts.banner = "Usage: #{@script_name} [options] <command>"
  694. opts.separator('Manage a Metasploit Framework database and web service')
  695. opts.separator('')
  696. opts.separator('General Options:')
  697. opts.on('--component COMPONENT', @components + ['all'], 'Component used with provided command (default: all)',
  698. " (#{@components.join(', ')})") { |component|
  699. @options[:component] = component.to_sym
  700. }
  701. opts.on('-d', '--debug', 'Enable debug output') { |d| @options[:debug] = d }
  702. opts.on('-h', '--help', 'Show this help message') {
  703. puts opts
  704. exit
  705. }
  706. opts.separator('')
  707. opts.separator('Database Options:')
  708. opts.on('--msf-db-name NAME', "Database name (default: #{@options[:msf_db_name]})") { |n|
  709. @options[:msf_db_name] = n
  710. }
  711. opts.on('--msf-db-user-name USER', "Database username (default: #{@options[:msf_db_user]})") { |u|
  712. @options[:msf_db_user] = u
  713. }
  714. opts.on('--msf-test-db-name NAME', "Test database name (default: #{@options[:msftest_db_name]})") { |n|
  715. @options[:msftest_db_name] = n
  716. }
  717. opts.on('--msf-test-db-user-name USER', "Test database username (default: #{@options[:msftest_db_user]})") { |u|
  718. @options[:msftest_db_user] = u
  719. }
  720. opts.on('--db-port PORT', Integer, "Database port (default: #{@options[:db_port]})") { |p|
  721. @options[:db_port] = p
  722. }
  723. opts.on('--db-pool MAX', Integer, "Database connection pool size (default: #{@options[:db_pool]})") { |m|
  724. @options[:db_pool] = m
  725. }
  726. opts.separator('')
  727. opts.separator('Web Service Options:')
  728. opts.on('-a', '--address ADDRESS',
  729. "Bind to host address (default: #{@options[:address]})") { |a|
  730. @options[:address] = a
  731. }
  732. opts.on('-p', '--port PORT', Integer,
  733. "Web service port (default: #{@options[:port]})") { |p|
  734. @options[:port] = p
  735. }
  736. opts.on('--[no-]ssl', "Enable SSL (default: #{@options[:ssl]})") { |s| @options[:ssl] = s }
  737. opts.on('--ssl-key-file PATH', "Path to private key (default: #{@options[:ssl_key]})") { |p|
  738. @options[:ssl_key] = p
  739. }
  740. opts.on('--ssl-cert-file PATH', "Path to certificate (default: #{@options[:ssl_cert]})") { |p|
  741. @options[:ssl_cert] = p
  742. }
  743. opts.on('--[no-]ssl-disable-verify',
  744. "Disables (optional) client cert requests (default: #{@options[:ssl_disable_verify]})") { |v|
  745. @options[:ssl_disable_verify] = v
  746. }
  747. opts.on('--environment ENV', @environments,
  748. "Web service framework environment (default: #{@options[:ws_env]})",
  749. " (#{@environments.join(', ')})") { |e|
  750. @options[:ws_env] = e
  751. }
  752. opts.on('--retry-max MAX', Integer,
  753. "Maximum number of web service connect attempts (default: #{@options[:retry_max]})") { |m|
  754. @options[:retry_max] = m
  755. }
  756. opts.on('--retry-delay DELAY', Float,
  757. "Delay in seconds between web service connect attempts (default: #{@options[:retry_delay]})") { |d|
  758. @options[:retry_delay] = d
  759. }
  760. opts.on('--user USER', 'Initial web service admin username') { |u|
  761. @options[:ws_user] = u
  762. }
  763. opts.separator('')
  764. opts.separator(subtext)
  765. end
  766. parser.parse!(args)
  767. if args.length != 1
  768. puts parser
  769. abort
  770. end
  771. @options
  772. end
  773. def invoke_command(commands, component, command)
  774. method = commands[component][command]
  775. if !method.nil?
  776. send(method)
  777. else
  778. puts "Error: unrecognized command '#{command}' for #{component}"
  779. end
  780. end
  781. def has_requirements
  782. ret_val = true
  783. postgresql_cmds = %w(psql pg_ctl initdb createdb)
  784. other_cmds = %w(bundle thin)
  785. missing_msg = 'Missing requirement: %<name>s does not appear to be installed or is not in the environment path'
  786. unless postgresql_cmds.all? { |cmd| !Msf::Util::Helper.which(cmd).nil? }
  787. puts missing_msg % { name: 'PostgreSQL' }
  788. ret_val = false
  789. end
  790. other_cmds.each do |cmd|
  791. if Msf::Util::Helper.which(cmd).nil?
  792. puts missing_msg % { name: "'#{cmd}'" }
  793. ret_val = false
  794. end
  795. end
  796. ret_val
  797. end
  798. if $PROGRAM_NAME == __FILE__
  799. # Bomb out if we're root
  800. if !Gem.win_platform? && Process.uid.zero?
  801. puts "Please run #{@script_name} as a non-root user"
  802. abort
  803. end
  804. unless has_requirements
  805. abort
  806. end
  807. # map component commands to methods
  808. commands = {
  809. database: {
  810. init: :init_db,
  811. reinit: :reinit_db,
  812. delete: :delete_db,
  813. status: :status_db,
  814. start: :start_db,
  815. stop: :stop_db,
  816. restart: :restart_db
  817. },
  818. webservice: {
  819. init: :init_web_service,
  820. reinit: :reinit_web_service,
  821. delete: :delete_web_service,
  822. status: :status_web_service,
  823. start: :start_web_service,
  824. stop: :stop_web_service,
  825. restart: :restart_web_service
  826. }
  827. }
  828. parse_args(ARGV)
  829. command = ARGV[0].to_sym
  830. if @options[:component] == :all
  831. @components.each { |component|
  832. invoke_command(commands, component.to_sym, command)
  833. }
  834. else
  835. invoke_command(commands, @options[:component], command)
  836. end
  837. end