Skip to content

Commit 9b6259e

Browse files
committed
Land rapid7#3569 - Updated smb_enumshares to support spidering
2 parents f25bb73 + f520616 commit 9b6259e

File tree

1 file changed

+151
-55
lines changed

1 file changed

+151
-55
lines changed

modules/auxiliary/scanner/smb/smb_enumshares.rb

Lines changed: 151 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def initialize(info={})
3333
'hdm',
3434
'nebulus',
3535
'sinn3r',
36-
'r3dy'
36+
'r3dy',
37+
'altonjx'
3738
],
3839
'License' => MSF_LICENSE,
3940
'DefaultOptions' =>
@@ -44,7 +45,11 @@ def initialize(info={})
4445

4546
register_options(
4647
[
47-
OptBool.new('DIR_SHARE', [true, 'Show all the folders and files', false ]),
48+
OptBool.new('SpiderShares', [false, 'Spider shares recursively', false]),
49+
OptBool.new('ShowFiles', [true, 'Show detailed information when spidering', false]),
50+
OptBool.new('SpiderProfiles', [false, 'Spider only user profiles when share = C$', true]),
51+
OptEnum.new('LogSpider', [false, '0 = disabled, 1 = CSV, 2 = table (txt), 3 = one liner (txt)', 3, [0,1,2,3]]),
52+
OptInt.new('MaxDepth', [true, 'Max number of subdirectories to spider', 999]),
4853
OptBool.new('USE_SRVSVC_ONLY', [true, 'List shares only with SRVSVC', false ])
4954
], self.class)
5055

@@ -75,7 +80,7 @@ def to_unix_time(thi, tlo)
7580
t.strftime("%m-%d-%Y %H:%M:%S")
7681
end
7782

78-
def eval_host(ip, share)
83+
def eval_host(ip, share, subdir = "")
7984
read = write = false
8085

8186
# srvsvc adds a null byte that needs to be removed
@@ -132,7 +137,7 @@ def eval_host(ip, share)
132137

133138
return read,write,msg,nil if skip
134139

135-
rfd = self.simple.client.find_first("\\")
140+
rfd = self.simple.client.find_first("#{subdir}\\*")
136141
read = true if rfd != nil
137142

138143
# Test writable
@@ -183,7 +188,7 @@ def lanman_netshareenum(ip, rport, info)
183188
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
184189
if e.error_code == 0xC00000BB
185190
vprint_error("#{ip}:#{rport} - Got 0xC00000BB while enumerating shares, switching to srvsvc...")
186-
datastore['USE_SRVSVC_ONLY'] = true # Make sure the module is aware of this state
191+
@srvsvc = true # Make sure the module is aware of this state
187192
return srvsvc_netshareenum(ip)
188193
end
189194
end
@@ -276,67 +281,157 @@ def srvsvc_netshareenum(ip)
276281
shares
277282
end
278283

284+
def get_user_dirs(ip, share, base, sub_dirs)
285+
dirs = []
286+
usernames = []
287+
288+
begin
289+
read,write,type,files = eval_host(ip, share, base)
290+
# files or type could return nil due to various conditions
291+
return dirs if files.nil?
292+
files.each do |f|
293+
if f[0] != "." and f[0] != ".."
294+
usernames.push(f[0])
295+
end
296+
end
297+
usernames.each do |username|
298+
sub_dirs.each do |sub_dir|
299+
dirs.push("#{base}\\#{username}\\#{sub_dir}")
300+
end
301+
end
302+
return dirs
303+
rescue
304+
return dirs
305+
end
306+
end
307+
308+
def profile_options(ip, share)
309+
old_dirs = ['My Documents','Desktop']
310+
new_dirs = ['Desktop','Documents','Downloads','Music','Pictures','Videos']
311+
312+
dirs = get_user_dirs(ip, share, "Documents and Settings", old_dirs)
313+
if dirs.blank?
314+
dirs = get_user_dirs(ip, share, "Users", new_dirs)
315+
end
316+
return dirs
317+
end
318+
279319
def get_files_info(ip, rport, shares, info)
280320
read = false
281321
write = false
282322

323+
# Creating a separate file for each IP address's results.
324+
detailed_tbl = Rex::Ui::Text::Table.new(
325+
'Header' => "Spidered results for #{ip}.",
326+
'Indent' => 1,
327+
'Columns' => [ 'IP Address', 'Type', 'Share', 'Path', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size' ]
328+
)
329+
330+
logdata = ""
331+
283332
list = shares.collect {|e| e[0]}
284333
list.each do |x|
285-
read,write,type,files = eval_host(ip, x)
286-
if files and (read or write)
287-
header = "#{ip}:#{rport}"
288-
if simple.client.default_domain and simple.client.default_name
289-
header << " \\\\#{simple.client.default_domain}"
290-
end
291-
header << "\\#{simple.client.default_name}\\#{x}" if simple.client.default_name
292-
header << " (#{type})" if type
293-
header << " Readable" if read
294-
header << " Writable" if write
295-
296-
tbl = Rex::Ui::Text::Table.new(
297-
'Header' => header,
298-
'Indent' => 1,
299-
'Columns' => [ 'Type', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size' ]
300-
)
301-
302-
f_types = {
303-
1 => 'RO', 2 => 'HIDDEN', 4 => 'SYS', 8 => 'VOL',
304-
16 => 'DIR', 32 => 'ARC', 64 => 'DEV', 128 => 'FILE'
305-
}
306-
307-
files.each do |file|
308-
if file[0] and file[0] != '.' and file[0] != '..'
309-
info = file[1]['info']
310-
fa = f_types[file[1]['attr']] # Item type
311-
fname = file[0] # Filename
312-
tcr = to_unix_time(info[3], info[2]) # Created
313-
tac = to_unix_time(info[5], info[4]) # Accessed
314-
twr = to_unix_time(info[7], info[6]) # Written
315-
tch = to_unix_time(info[9], info[8]) # Changed
316-
sz = info[12] + info[13] # Size
317-
318-
# Filename is too long for the UI table, cut it.
319-
fname = "#{fname[0, 35]}..." if fname.length > 35
320-
321-
tbl << [fa || 'Unknown', fname, tcr, tac, twr, tch, sz]
334+
x = x.strip
335+
if x == "ADMIN$" or x == "IPC$"
336+
next
337+
end
338+
if not datastore['ShowFiles']
339+
print_status("#{ip}:#{rport} - Spidering #{x}.")
340+
end
341+
subdirs = [""]
342+
if x.strip() == "C$" and datastore['SpiderProfiles']
343+
subdirs = profile_options(ip, x)
344+
end
345+
while subdirs.length > 0
346+
depth = subdirs[0].count("\\")
347+
if datastore['SpiderProfiles'] and x == "C$"
348+
if depth-2 > datastore['MaxDepth']
349+
subdirs.shift
350+
next
351+
end
352+
else
353+
if depth > datastore['MaxDepth']
354+
subdirs.shift
355+
next
322356
end
323357
end
358+
read,write,type,files = eval_host(ip, x, subdirs[0])
359+
if files and (read or write)
360+
if files.length < 3
361+
subdirs.shift
362+
next
363+
end
364+
header = "#{ip}:#{rport}"
365+
if simple.client.default_domain and simple.client.default_name
366+
header << " \\\\#{simple.client.default_domain}"
367+
end
368+
header << "\\#{x.sub("C$","C$\\")}" if simple.client.default_name
369+
header << subdirs[0]
324370

325-
print_good(tbl.to_s)
326-
unless tbl.rows.empty?
327-
p = store_loot('smb.shares', 'text/csv', ip, tbl.to_csv)
328-
print_good("#{x} info saved in: #{p.to_s}")
371+
pretty_tbl = Rex::Ui::Text::Table.new(
372+
'Header' => header,
373+
'Indent' => 1,
374+
'Columns' => [ 'Type', 'Name', 'Created', 'Accessed', 'Written', 'Changed', 'Size' ]
375+
)
376+
377+
f_types = {
378+
1 => 'RO', 2 => 'HIDDEN', 4 => 'SYS', 8 => 'VOL',
379+
16 => 'DIR', 32 => 'ARC', 64 => 'DEV', 128 => 'FILE'
380+
}
381+
382+
files.each do |file|
383+
if file[0] and file[0] != '.' and file[0] != '..'
384+
info = file[1]['info']
385+
fa = f_types[file[1]['attr']] # Item type
386+
fname = file[0] # Filename
387+
tcr = to_unix_time(info[3], info[2]) # Created
388+
tac = to_unix_time(info[5], info[4]) # Accessed
389+
twr = to_unix_time(info[7], info[6]) # Written
390+
tch = to_unix_time(info[9], info[8]) # Changed
391+
sz = info[12] + info[13] # Size
392+
393+
# Filename is too long for the UI table, cut it.
394+
fname = "#{fname[0, 35]}..." if fname.length > 35
395+
396+
# Add subdirectories to list to use if SpiderShare is enabled.
397+
if fa == "DIR" or (fa == nil and sz == 0)
398+
subdirs.push(subdirs[0] + "\\" + fname)
399+
end
400+
401+
pretty_tbl << [fa || 'Unknown', fname, tcr, tac, twr, tch, sz]
402+
detailed_tbl << ["#{ip}", fa || 'Unknown', "#{x}", subdirs[0] + "\\", fname, tcr, tac, twr, tch, sz]
403+
logdata << "#{ip}\\#{x.sub("C$","C$\\")}#{subdirs[0]}\\#{fname}\n"
404+
405+
end
406+
end
407+
print_good(pretty_tbl.to_s) if datastore['ShowFiles']
329408
end
409+
subdirs.shift
410+
end
411+
print_status("#{ip}:#{rport} - Spider #{x} complete.") unless datastore['ShowFiles'] == true
412+
end
413+
unless detailed_tbl.rows.empty?
414+
if datastore['LogSpider'] == '1'
415+
p = store_loot('smb.enumshares', 'text/csv', ip, detailed_tbl.to_csv)
416+
print_good("#{ip} - info saved in: #{p.to_s}")
417+
elsif datastore['LogSpider'] == '2'
418+
p = store_loot('smb.enumshares', 'text', ip, detailed_tbl)
419+
print_good("#{ip} - info saved in: #{p.to_s}")
420+
elsif datastore['LogSpider'] == '3'
421+
p = store_loot('smb.enumshares', 'text', ip, logdata)
422+
print_good("#{ip} - info saved in: #{p.to_s}")
330423
end
331424
end
332425
end
333426

334-
def cleanup
335-
datastore['RPORT'] = @rport
336-
datastore['SMBDirect'] = @smb_redirect
337-
datastore['USE_SRVSVC_ONLY'] = @srvsvc
427+
def rport
428+
@rport || datastore['RPORT']
338429
end
339430

431+
# Overrides the one in smb.rb
432+
def smb_direct
433+
@smb_redirect || datastore['SMBDirect']
434+
end
340435

341436
def run_host(ip)
342437
@rport = datastore['RPORT']
@@ -345,13 +440,13 @@ def run_host(ip)
345440
shares = []
346441

347442
[[139, false], [445, true]].each do |info|
348-
datastore['RPORT'] = info[0]
349-
datastore['SMBDirect'] = info[1]
443+
@rport = info[0]
444+
@smb_redirect = info[1]
350445

351446
begin
352447
connect
353448
smb_login
354-
if datastore['USE_SRVSVC_ONLY']
449+
if @srvsvc
355450
shares = srvsvc_netshareenum(ip)
356451
else
357452
shares = lanman_netshareenum(ip, rport, info)
@@ -363,7 +458,7 @@ def run_host(ip)
363458
if shares.empty?
364459
print_status("#{ip}:#{rport} - No shares collected")
365460
else
366-
shares_info = shares.map{|x| "#{ip}: #{x[0]} - (#{x[1]}) #{x[2]}" }.join(", ")
461+
shares_info = shares.map{|x| "#{ip}:#{rport} - #{x[0]} - (#{x[1]}) #{x[2]}" }.join(", ")
367462
shares_info.split(", ").each { |share|
368463
print_good share
369464
}
@@ -376,7 +471,7 @@ def run_host(ip)
376471
:update => :unique_data
377472
)
378473

379-
if datastore['DIR_SHARE']
474+
if datastore['SpiderShares']
380475
get_files_info(ip, rport, shares, info)
381476
end
382477

@@ -414,3 +509,4 @@ def run_host(ip)
414509
end
415510
end
416511
end
512+

0 commit comments

Comments
 (0)