diff --git a/CHANGELOG b/CHANGELOG index 0794b4815..14174423a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Make smb-ls able to leverage results from smb-enum-shares or list of + shares specified on command line. [Pierre Lalet] + o [NSE] Fix X509 cert date parsing for dates after 2049. Reported by Teppo Turtiainen. [Daniel Miller] diff --git a/scripts/smb-enum-shares.nse b/scripts/smb-enum-shares.nse index 8e43f284f..3f977420a 100644 --- a/scripts/smb-enum-shares.nse +++ b/scripts/smb-enum-shares.nse @@ -126,6 +126,10 @@ action = function(host) end response.account_used = string.format("%s%s", domain, stdnse.string_or_blank(username, '')) + if host.registry['smb_shares'] == nil then + host.registry['smb_shares'] = {} + end + for i = 1, #shares, 1 do local share = shares[i] local share_output = stdnse.output_table() @@ -135,6 +139,8 @@ action = function(host) -- A share of 'NT_STATUS_OBJECT_NAME_NOT_FOUND' indicates this isn't a fileshare if(share['user_can_write'] == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then share_output["Type"] = "Not a file share" + else + table.insert(host.registry['smb_shares'], share.name) end else local details = share['details'] @@ -144,6 +150,12 @@ action = function(host) share_output["Users"] = details.current_users share_output["Max Users"] = details.max_users share_output["Path"] = details.path + + if (share_output["Type"] == "STYPE_DISKTREE" or + share_output["Type"] == "STYPE_DISKTREE_TEMPORARY" or + share_output["Type"] == "STYPE_DISKTREE_HIDDEN") then + table.insert(host.registry['smb_shares'], share.name) + end end -- Print details for a file share if(share['anonymous_can_read'] and share['anonymous_can_write']) then @@ -172,6 +184,10 @@ action = function(host) response[share.name] = share_output end + if next(host.registry['smb_shares']) == nil then + host.registry['smb_shares'] = nil + end + return response end diff --git a/scripts/smb-ls.nse b/scripts/smb-ls.nse index 87c72b044..8f8960e6e 100644 --- a/scripts/smb-ls.nse +++ b/scripts/smb-ls.nse @@ -30,10 +30,11 @@ The output is intended to resemble the output of the UNIX ls comman -- | 2007-12-02 00:42:40 WINDOWS -- |_ 2007-12-02 00:22:38 wmpub -- --- @args smb-ls.share the share to connect to --- @args smb-ls.path the path, relative to the share to list the contents from +-- @args smb-ls.share [optional] the share to connect to +-- @args smb-ls.shares [optional] a colon-separated list of shares to connect to +-- @args smb-ls.path [optional] the path, relative to the share to list the contents from -- @args smb-ls.pattern [optional] the search pattern to execute (default: *) --- @args smb-ls.maxdepth [optional] the maximum depth to recurse into a directory +-- @args smb-ls.maxdepth [optional] the maximum depth to recurse into a directory (default: no recursion) -- @args smb-ls.maxfiles [optional] return only a certain amount of files -- @args smb-ls.checksum [optional] download each file and calculate a SHA1 checksum -- @@ -41,16 +42,20 @@ The output is intended to resemble the output of the UNIX ls comman author = "Patrik Karlsson" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "safe"} +dependencies = {"smb-enum-shares"} +local arg_shares = stdnse.get_script_args(SCRIPT_NAME .. '.shares') local arg_share = stdnse.get_script_args(SCRIPT_NAME .. '.share') -local arg_path = stdnse.get_script_args(SCRIPT_NAME .. '.path') +local arg_path = stdnse.get_script_args(SCRIPT_NAME .. '.path') or '\\' local arg_pattern = stdnse.get_script_args(SCRIPT_NAME .. '.pattern') or '*' local arg_maxfiles = tonumber(stdnse.get_script_args(SCRIPT_NAME .. '.maxfiles')) -local arg_maxdepth = tonumber(stdnse.get_script_args(SCRIPT_NAME .. '.maxdepth')) +local arg_maxdepth = stdnse.get_script_args(SCRIPT_NAME .. '.maxdepth') local arg_checksum = stdnse.get_script_args(SCRIPT_NAME .. '.checksum') hostrule = function(host) - return ( smb.get_port(host) ~= nil and arg_share and arg_path ) + return ( smb.get_port(host) ~= nil and + (arg_shares or arg_share + or host.registry['smb_shares'] ~= nil) ) end -- checks whether the file entry is a directory @@ -61,68 +66,99 @@ end local function fail(err) return ("\n ERROR: %s"):format(err or "") end action = function(host) - local status, smbstate = smb.start_ex(host, true, true, arg_share, nil, nil, nil) - if ( not(status) ) then - return fail("Failed to authenticate to server (" .. smbstate .. ")") + + -- give priority to specified shares if specified + if arg_shares ~= nil then + arg_shares = stdnse.strsplit(":", arg_shares) + elseif arg_share ~= nil then + arg_shares = {arg_share} + else + arg_shares = host.registry['smb_shares'] end - -- remove leading slash - arg_path = ( arg_path:sub(1,2) == '\\' and arg_path:sub(2) or arg_path ) + -- arg_maxdepth defaults to 1 (no recursion) + if arg_maxdepth == nil then + arg_maxdepth = 1 + else + arg_maxdepth = tonumber(arg_maxdepth) + end - -- fixup checksum argument - arg_checksum = ( arg_checksum == 'true' or arg_checksum == '1' ) and true or false + local output = {} - local options = { max_depth = arg_maxdepth, max_files = arg_maxfiles } - local depth, path, output, dirs = 0, arg_path, {}, {} - local file_count, dir_count, total_bytes = 0, 0, 0 + for _, share in ipairs(arg_shares) do + local status, smbstate = smb.start_ex(host, true, true, share, + nil, nil, nil) + if ( not(status) ) then + table.insert( + output, + ("Failed to authenticate to server (%s) for directory of \\\\%s\\%s%s"):format(smbstate, stdnse.get_hostname(host), share, arg_path)) + table.insert(output, "") + else - repeat - local lstab = tab.new((arg_checksum and 4 or 3)) + table.insert(output, "") - for fe in smb.find_files(smbstate, path .. '\\' .. arg_pattern, options ) do - if ( arg_checksum and not(is_dir(fe)) ) then - local status, content = smb.file_read(host, arg_share, path .. '\\' .. fe.fname, nil, {file_create_disposition=1}) - local sha1 = ( status and stdnse.tohex(openssl.sha1(content)) or "" ) - tab.addrow(lstab, fe.created, (is_dir(fe) and '' or fe.eof), fe.fname, sha1) - else - tab.addrow(lstab, fe.created, (is_dir(fe) and '' or fe.eof), fe.fname) - end + -- remove leading slash + arg_path = ( arg_path:sub(1,2) == '\\' and arg_path:sub(2) or arg_path ) - arg_maxfiles = ( arg_maxfiles and arg_maxfiles - 1 ) - if ( arg_maxfiles == 0 ) then - break - end + -- fixup checksum argument + arg_checksum = ( arg_checksum == 'true' or arg_checksum == '1' ) and true or false - if ( is_dir(fe) ) then - dir_count = dir_count + 1 - if ( fe.fname ~= '.' and fe.fname ~= '..' ) then - table.insert(dirs, { depth = depth + 1, path = path .. '\\' .. fe.fname } ) - end - else - total_bytes = total_bytes + fe.eof - file_count = file_count + 1 - end - end - table.insert(output, { name = ("Directory of %s"):format( '\\\\' .. stdnse.get_hostname(host) .. '\\' .. arg_share .. path), tab.dump(lstab) }) + local options = { max_depth = arg_maxdepth, max_files = arg_maxfiles } + local depth, path, dirs = 0, arg_path, {} + local file_count, dir_count, total_bytes = 0, 0, 0 - path = nil - if ( #dirs ~= 0 ) then - local dir = table.remove(dirs, 1) - depth = dir.depth - if ( not(arg_maxdepth) or ( dir.depth < arg_maxdepth ) ) then - path = dir.path - table.insert(output, "") - end - end - until(not(path) or arg_maxfiles == 0) + repeat + -- we need three columns per row, plus one for checksum if + -- requested + local lstab = tab.new((arg_checksum and 4 or 3)) - smb.stop(smbstate) + for fe in smb.find_files(smbstate, path .. '\\' .. arg_pattern, options ) do + if ( arg_checksum and not(is_dir(fe)) ) then + local status, content = smb.file_read(host, share, path .. '\\' .. fe.fname, nil, {file_create_disposition=1}) + local sha1 = ( status and stdnse.tohex(openssl.sha1(content)) or "" ) + tab.addrow(lstab, fe.created, (is_dir(fe) and '' or fe.eof), fe.fname, sha1) + else + tab.addrow(lstab, fe.created, (is_dir(fe) and '' or fe.eof), fe.fname) + end - local summary = { name = "Total Files Listed:", - ("%8d File(s)\t%d bytes"):format(file_count, total_bytes), - ("%8d Dir(s)"):format(dir_count) } - table.insert(output, "") - table.insert(output, summary) + arg_maxfiles = ( arg_maxfiles and arg_maxfiles - 1 ) + if ( arg_maxfiles == 0 ) then + break + end + + if ( is_dir(fe) ) then + dir_count = dir_count + 1 + if ( fe.fname ~= '.' and fe.fname ~= '..' ) then + table.insert(dirs, { depth = depth + 1, path = path .. '\\' .. fe.fname } ) + end + else + total_bytes = total_bytes + fe.eof + file_count = file_count + 1 + end + end + table.insert(output, { name = ("Directory of %s"):format( '\\\\' .. stdnse.get_hostname(host) .. '\\' .. share .. path), tab.dump(lstab) }) + + path = nil + if ( #dirs ~= 0 ) then + local dir = table.remove(dirs, 1) + depth = dir.depth + if ( not(arg_maxdepth) or ( dir.depth < arg_maxdepth ) ) then + path = dir.path + table.insert(output, "") + end + end + until(not(path) or arg_maxfiles == 0) + + smb.stop(smbstate) + + local summary = { name = "Total Files Listed:", + ("%8d File(s)\t%d bytes"):format(file_count, total_bytes), + ("%8d Dir(s)"):format(dir_count) } + table.insert(output, "") + table.insert(output, summary) + table.insert(output, "") + end + end return stdnse.format_output(true, output) end