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 32KB

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