msfdb 32 KB

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