local bit = require 'bit'
local smb = require 'smb'
local string = require 'string'
local stdnse = require 'stdnse'
local ls = require 'ls'
local openssl= stdnse.silent_require 'openssl'
description = [[
Attempts to retrieve useful information about files shared on SMB volumes.
The output is intended to resemble the output of the UNIX ls
command.
]]
---
-- @usage
-- nmap -p 445 --script smb-ls --script-args 'share=c$,path=\temp'
-- nmap -p 445 --script smb-enum-shares,smb-ls
--
-- @args smb-ls.share (or smb-ls.shares) the share (or a colon-separated list
-- of shares) to connect to (default: use shares found by smb-enum-shares)
-- @args smb-ls.path the path, relative to the share to list the contents from
-- (default: root of the share)
-- @args smb-ls.pattern the search pattern to execute (default: *)
-- @args smb-ls.checksum download each file and calculate a checksum
-- (default: false)
--
-- @output
-- Host script results:
-- | smb-ls:
-- | Volume \\192.168.56.101\c$\
-- | SIZE TIME FILENAME
-- | 0 2007-12-02 00:20:09 AUTOEXEC.BAT
-- | 0 2007-12-02 00:20:09 CONFIG.SYS
-- | 2007-12-02 00:53:39 Documents and Settings
-- | 2009-09-08 13:26:10 e5a6b742d36facb19c5192852c43
-- | 2008-12-01 02:06:29 Inetpub
-- | 94720 2007-02-18 00:31:38 msizap.exe
-- | 2007-12-02 00:55:01 Program Files
-- | 2008-12-01 02:05:52 temp
-- | 2011-12-16 14:40:18 usr
-- | 2007-12-02 00:42:40 WINDOWS
-- | 2007-12-02 00:22:38 wmpub
-- |_
--
-- @xmloutput
--
--
--
--
-- 0
-- 2007-12-02 00:20:09
-- AUTOEXEC.BAT
--
--
-- 0
-- 2007-12-02 00:20:09
-- CONFIG.SYS
--
--
-- <DIR>
-- 2007-12-02 00:53:39
-- Documents and Settings
--
--
-- <DIR>
-- 2009-09-08 13:26:10
-- e5a6b742d36facb19c5192852c43
--
--
-- <DIR>
-- 2008-12-01 02:06:29
-- Inetpub
--
--
-- 94720
-- 2007-02-18 00:31:38
-- msizap.exe
--
--
-- <DIR>
-- 2007-12-02 00:55:01
-- Program Files
--
--
-- <DIR>
-- 2008-12-01 02:05:52
-- temp
--
--
-- <DIR>
-- 2011-12-16 14:40:18
-- usr
--
--
-- <DIR>
-- 2007-12-02 00:42:40
-- WINDOWS
--
--
-- <DIR>
-- 2007-12-02 00:22:38
-- wmpub
--
--
-- \\192.168.1.2\Downloads
--
--
author = "Patrik Karlsson"
license = "Same as Nmap--See https://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') or '\\'
local arg_pattern = stdnse.get_script_args(SCRIPT_NAME .. '.pattern') or '*'
hostrule = function(host)
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
local function is_dir(fe)
return ( bit.band(fe.attrs, 16) == 16 )
end
local function list_files(host, share, smbstate, path, options, output, maxdepth, basedir)
basedir = basedir or ""
local continue
for fe in smb.find_files(smbstate, path .. '\\' .. arg_pattern, options) do
if basedir == "" or (fe.fname ~= "." and fe.fname ~= "..") then
if ls.config('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 ""
continue = ls.add_file(output, {is_dir(fe) and '' or fe.eof,
fe.created, basedir .. fe.fname, sha1})
else
continue = ls.add_file(output, {is_dir(fe) and '' or fe.eof,
fe.created, basedir .. fe.fname})
end
if not continue then
return false
end
if is_dir(fe) then
continue = true
if maxdepth > 0 then
continue = list_files(host, share, smbstate,
path .. '\\' .. fe.fname, options,
output, maxdepth - 1,
basedir .. fe.fname .. '\\')
elseif maxdepth < 0 then
continue = list_files(host, share, smbstate,
path .. '\\' .. fe.fname, options,
output, -1,
basedir .. fe.fname .. '\\')
end
if not continue then
return false
end
end
end
end
return true
end
action = function(host)
-- 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
local output = ls.new_listing()
for _, share in ipairs(arg_shares) do
local status, smbstate = smb.start_ex(host, true, true, share,
nil, nil, nil)
if ( not(status) ) then
ls.report_error(
output,
("Failed to authenticate to server (%s) for directory of \\\\%s\\%s%s"):format(smbstate, stdnse.get_hostname(host), share, arg_path))
else
-- remove leading slash
arg_path = ( arg_path:sub(1,2) == '\\' and arg_path:sub(2) or arg_path )
local options = {}
local depth, path, dirs = 0, arg_path, {}
local file_count, dir_count, total_bytes = 0, 0, 0
local continue = true
ls.new_vol(
output,
'\\\\' .. stdnse.get_hostname(host) .. '\\' .. share .. path,
false)
continue = list_files(host, share, smbstate, path, options,
output, ls.config('maxdepth'))
if not continue then
ls.report_info(
output,
string.format("maxfiles limit reached (%d)", ls.config('maxfiles')))
end
ls.end_vol(output)
smb.stop(smbstate)
end
end
return ls.end_listing(output)
end