diff --git a/modules/auxiliary/scanner/smb/smb_enumshares.rb b/modules/auxiliary/scanner/smb/smb_enumshares.rb index 42d86801f2..8359d7f6a6 100644 --- a/modules/auxiliary/scanner/smb/smb_enumshares.rb +++ b/modules/auxiliary/scanner/smb/smb_enumshares.rb @@ -33,6 +33,7 @@ class Metasploit3 < Msf::Auxiliary 'hdm', 'nebulus', 'sinn3r', + 'altonjx', 'r3dy' ], 'License' => MSF_LICENSE, @@ -44,7 +45,11 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptBool.new('DIR_SHARE', [true, 'Show all the folders and files', false ]), + OptBool.new('SpiderShares', [false, 'Spider shares recursively', false]), + OptBool.new('VERBOSE', [true, 'Show detailed information when spidering', true]), + OptBool.new('SpiderProfiles', [false, 'Spider only user profiles when share = C$', true]), + OptInt.new('LogSpider', [false, '1 = CSV, 2 = table (txt), 3 = one liner (txt)', 3]), + OptInt.new('MaxDepth', [true, 'Max number of subdirectories to spider', 999]), OptBool.new('USE_SRVSVC_ONLY', [true, 'List shares only with SRVSVC', false ]) ], self.class) @@ -75,7 +80,7 @@ class Metasploit3 < Msf::Auxiliary t.strftime("%m-%d-%Y %H:%M:%S") end - def eval_host(ip, share) + def eval_host(ip, share, subdir="") read = write = false # srvsvc adds a null byte that needs to be removed @@ -132,7 +137,7 @@ class Metasploit3 < Msf::Auxiliary return read,write,msg,nil if skip - rfd = self.simple.client.find_first("\\") + rfd = self.simple.client.find_first("#{subdir}\\*") read = true if rfd != nil # Test writable @@ -276,57 +281,148 @@ class Metasploit3 < Msf::Auxiliary shares end + def profile_options(ip, share) + usernames = [] + old_dirs = ['My Documents','Desktop'] + new_dirs = ['Desktop','Documents','Downloads','Music','Pictures','Videos'] + subdirs = [] + begin + read,write,type,files = eval_host(ip, share, "Documents and Settings") + files.each do |f| + if f[0] != "." and f[0] != ".." + usernames.push(f[0]) + end + end + + # Return usernames along with their profile directories. + usernames.each do |username| + old_dirs.each do |dir| + subdirs.push("Documents and Settings\\#{username}\\#{dir}") + end + end + rescue + read,write,type,files = eval_host(ip, share, "Users") + files.each do |f| + if f[0] != "." and f[0] != ".." + usernames.push(f[0]) + end + end + + # Return usernames along with their profile directories. + usernames.each do |username| + new_dirs.each do |dir| + subdirs.push("Users\\#{username}\\#{dir}") + end + end + end + return subdirs + end + def get_files_info(ip, rport, shares, info) + read = false write = false + # Creating a separate file for each IP address's results. + detailed_tbl = Rex::Ui::Text::Table.new( + 'Header' => "Spidered results for #{ip}.", + 'Indent' => 1, + 'Columns' => [ 'IP Address', 'Type', 'Share', 'Path', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size' ] + ) + + logdata = "" + list = shares.collect {|e| e[0]} list.each do |x| - read,write,type,files = eval_host(ip, x) - if files and (read or write) - header = "#{ip}:#{rport}" - if simple.client.default_domain and simple.client.default_name - header << " \\\\#{simple.client.default_domain}" - end - header << "\\#{simple.client.default_name}\\#{x}" if simple.client.default_name - header << " (#{type})" if type - header << " Readable" if read - header << " Writable" if write + if x.strip == "ADMIN$" + next + end + if not datastore['VERBOSE'] + print_status("#{ip}:#{rport} - Spidering #{x}.") + end + subdirs = [""] + if x.strip() == "C$" and datastore['SpiderProfiles'] + subdirs = profile_options(ip, x) + end - tbl = Rex::Ui::Text::Table.new( - 'Header' => header, - 'Indent' => 1, - 'Columns' => [ 'Type', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size' ] - ) - - f_types = { - 1 => 'RO', 2 => 'HIDDEN', 4 => 'SYS', 8 => 'VOL', - 16 => 'DIR', 32 => 'ARC', 64 => 'DEV', 128 => 'FILE' - } - - files.each do |file| - if file[0] and file[0] != '.' and file[0] != '..' - info = file[1]['info'] - fa = f_types[file[1]['attr']] # Item type - fname = file[0] # Filename - tcr = to_unix_time(info[3], info[2]) # Created - tac = to_unix_time(info[5], info[4]) # Accessed - twr = to_unix_time(info[7], info[6]) # Written - tch = to_unix_time(info[9], info[8]) # Changed - sz = info[12] + info[13] # Size - - # Filename is too long for the UI table, cut it. - fname = "#{fname[0, 35]}..." if fname.length > 35 - - tbl << [fa || 'Unknown', fname, tcr, tac, twr, tch, sz] + while subdirs.length > 0 + depth = subdirs[0].count("\\") + if datastore['SpiderProfiles'] and x == "C$" + if depth-2 > datastore['MaxDepth'] + subdirs.shift + next + end + else + if depth > datastore['MaxDepth'] + subdirs.shift + next end end + read,write,type,files = eval_host(ip, x, subdirs[0]) + if files and (read or write) + if files.length < 3 + subdirs.shift + next + end + header = "#{ip}:#{rport}" + if simple.client.default_domain and simple.client.default_name + header << " \\\\#{simple.client.default_domain}" + end + header << "\\#{x.sub("C$","C$\\")}" if simple.client.default_name + header << subdirs[0] - print_good(tbl.to_s) - unless tbl.rows.empty? - p = store_loot('smb.shares', 'text/csv', ip, tbl.to_csv) - print_good("#{x} info saved in: #{p.to_s}") + pretty_tbl = Rex::Ui::Text::Table.new( + 'Header' => header, + 'Indent' => 1, + 'Columns' => [ 'Type', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size' ] + ) + + f_types = { + 1 => 'RO', 2 => 'HIDDEN', 4 => 'SYS', 8 => 'VOL', + 16 => 'DIR', 32 => 'ARC', 64 => 'DEV', 128 => 'FILE' + } + + files.each do |file| + if file[0] and file[0] != '.' and file[0] != '..' + info = file[1]['info'] + fa = f_types[file[1]['attr']] # Item type + fname = file[0] # Filename + tcr = to_unix_time(info[3], info[2]) # Created + tac = to_unix_time(info[5], info[4]) # Accessed + twr = to_unix_time(info[7], info[6]) # Written + tch = to_unix_time(info[9], info[8]) # Changed + sz = info[12] + info[13] # Size + + # Filename is too long for the UI table, cut it. + fname = "#{fname[0, 35]}..." if fname.length > 35 + + # Add subdirectories to list to use if SpiderShare is enabled. + if fa == "DIR" or (fa == nil and sz == 0) + subdirs.push(subdirs[0] + "\\" + fname) + end + + pretty_tbl << [fa || 'Unknown', fname, tcr, tac, twr, tch, sz] + detailed_tbl << ["#{ip}", fa || 'Unknown', "#{x}", subdirs[0] + "\\", fname, tcr, tac, twr, tch, sz] + logdata << "#{ip}\\#{x.sub("C$","C$\\")}#{subdirs[0]}\\#{fname}\n" + + end + end + vprint_good(pretty_tbl.to_s) end + subdirs.shift + end + print_status("#{ip}:#{rport} - Spider #{x} complete.") unless datastore['VERBOSE'] == true + end + unless detailed_tbl.rows.empty? + if datastore['LogSpider'] == 1 + p = store_loot('smb.enumshares', 'text/csv', ip, detailed_tbl.to_csv) + print_good("#{ip} - info saved in: #{p.to_s}") + elsif datastore['LogSpider'] == 2 + p = store_loot('smb.enumshares', 'text', ip, detailed_tbl) + print_good("#{ip} - info saved in: #{p.to_s}") + elsif datastore['LogSpider'] == 3 + p = store_loot('smb.enumshares', 'text', ip, logdata) + print_good("#{ip} - info saved in: #{p.to_s}") end end end @@ -363,7 +459,7 @@ class Metasploit3 < Msf::Auxiliary if shares.empty? print_status("#{ip}:#{rport} - No shares collected") else - shares_info = shares.map{|x| "#{ip}: #{x[0]} - (#{x[1]}) #{x[2]}" }.join(", ") + shares_info = shares.map{|x| "#{ip}:#{rport} - #{x[0]} - (#{x[1]}) #{x[2]}" }.join(", ") shares_info.split(", ").each { |share| print_good share } @@ -376,7 +472,7 @@ class Metasploit3 < Msf::Auxiliary :update => :unique_data ) - if datastore['DIR_SHARE'] + if datastore['SpiderShares'] get_files_info(ip, rport, shares, info) end