diff --git a/nselib/brute.lua b/nselib/brute.lua index 00ee8cee0..857ac052e 100644 --- a/nselib/brute.lua +++ b/nselib/brute.lua @@ -543,10 +543,10 @@ Engine = creds.Credentials:new( self.options.script_name, self.host, self.port ):add(response.username, response.password, response.state ) else self.credstore = self.credstore or {} - table.insert(self.credstore, response:toString() ) + table.insert(self.credstore, tostring(response) ) end - stdnse.debug1("Discovered account: %s", response:toString()) + stdnse.debug1("Discovered account: %s", tostring(response)) -- if we're running in passonly mode, and want to continue guessing -- we will have a problem as the username is always the same. @@ -702,13 +702,12 @@ Engine = valid_accounts = self.credstore end - local result = {} + local result = stdnse.output_table() -- Did we find any accounts, if so, do formatting if ( valid_accounts and #valid_accounts > 0 ) then - valid_accounts.name = self.options.title or "Accounts" - table.insert( result, valid_accounts ) + result[self.options.title or "Accounts"] = valid_accounts else - table.insert( result, {"No valid accounts found", name="Accounts"} ) + result.Accounts = "No valid accounts found" end -- calculate the average tps @@ -719,27 +718,21 @@ Engine = local tps = ( sum == 0 ) and ( self.counter / time_diff ) or ( sum / #self.tps ) -- Add the statistics to the result - local stats = {} - table.insert(stats, ("Performed %d guesses in %d seconds, average tps: %d"):format( self.counter, time_diff, tps ) ) - stats.name = "Statistics" - table.insert( result, stats ) + result.Statistics = ("Performed %d guesses in %d seconds, average tps: %d"):format( self.counter, time_diff, tps ) if ( self.options.max_guesses > 0 ) then -- we only display a warning if the guesses are equal to max_guesses for user, guesses in pairs(self.account_guesses) do if ( guesses == self.options.max_guesses ) then - table.insert( result, { name = "Information", - ("Guesses restricted to %d tries per account to avoid lockout"):format(self.options.max_guesses) } ) + result.Information = ("Guesses restricted to %d tries per account to avoid lockout"):format(self.options.max_guesses) break end end end - result = ( #result ) and stdnse.format_output( true, result ) or "" - -- Did any error occur? If so add this to the result. if ( self.error ) then - result = result .. (" \n ERROR: %s"):format( self.error ) + result.ERROR = self.error return false, result end return true, result diff --git a/nselib/creds.lua b/nselib/creds.lua index 022641b66..722a2e72d 100644 --- a/nselib/creds.lua +++ b/nselib/creds.lua @@ -261,9 +261,37 @@ Account = { (self.state and " - " .. self.state or "") ) end, + + --- Less-than operation for sorting + -- + -- Lexicographic comparison by user, pass, and state + __lt = function (a, b) + if a.user >= b.user then + return false + elseif a.pass >= b.pass then + return false + elseif a.state >= b.state then + return false + end + return true + end, } +-- Return a function suitable for use as a __pairs metamethod +-- which will cause the table to yield its values sorted by key. +local function sorted_pairs (sortby) + return function (t) + local order = stdnse.keys(t) + table.sort(order, sortby) + return coroutine.wrap(function() + for i,k in ipairs(order) do + coroutine.yield(k, t[k]) + end + end) + end +end + -- The credentials class Credentials = { @@ -378,63 +406,50 @@ Credentials = { local result = {} for v in self.storage:getAll() do - local h = ( v.host.ip or v.host ) - local svc = ("%s/%s"):format(v.port,v.service) - local c - if ( v.user and #v.user > 0 ) then - if StateMsg[v.state] then - c = ("%s:%s - %s"):format(v.user, v.pass, StateMsg[v.state]) - else - c = ("%s:%s"):format(v.user, v.pass) - end - else - if StateMsg[v.state] then - c = ("%s - %s"):format(v.pass, StateMsg[v.state]) - else - c = ("%s"):format(v.pass) - end - end - local script = v.scriptname - assert(type(h)=="string", "Could not determine a valid host") + if ( v.scriptname == self.scriptname or self.scriptname == ALL_DATA ) then + local h = ( v.host.ip or v.host ) + assert(type(h)=="string", "Could not determine a valid host") + local svc = ("%s/%s"):format(v.port,v.service) - if ( script == self.scriptname or self.scriptname == ALL_DATA ) then result[h] = result[h] or {} result[h][svc] = result[h][svc] or {} - table.insert( result[h][svc], c ) + table.insert( result[h][svc], Account:new( + v.user ~= "" and v.user or nil, + v.pass, + v.state + ) + ) end end - local output = {} - for hostname, host in pairs(result) do - local host_tbl = { name = hostname } - for svcname, service in pairs(host) do - local svc_tbl = { name = svcname } - for _, account in ipairs(service) do - table.insert(svc_tbl, account) - end + for _, host_tbl in pairs(result) do + for _, svc_tbl in pairs(host_tbl) do -- sort the accounts - table.sort( svc_tbl, function(a,b) return a 0 ) then - output = output[1][1] - output.name = nil - elseif ( self.host and #output > 0 ) then - output = output[1] - output.name = nil + setmetatable(result, { + __pairs = sorted_pairs( function(a, b) + return ipOps.compare_ip(a, "le", b) + end ) + }) + + local _ + if ( self.host and next(result) ) then + _, result = next(result) end - return (#output > 0 ) and output + if ( self.host and self.port and next(result) ) then + _, result = next(result) + end + return next(result) and result end, -- Saves credentials in the current object to file @@ -482,7 +497,7 @@ Credentials = { -- @return table suitable from stdnse.format_output __tostring = function(self) local all = self:getTable() - if ( all ) then return stdnse.format_output(true, all) end + if ( all ) then return tostring(all) end end, } diff --git a/scripts/creds-summary.nse b/scripts/creds-summary.nse index da91b853e..a596314ec 100644 --- a/scripts/creds-summary.nse +++ b/scripts/creds-summary.nse @@ -32,10 +32,10 @@ categories = {"auth", "default", "safe"} postrule = function() local all = creds.Credentials:new(creds.ALL_DATA) local tab = all:getTable() - if ( tab and #tab > 0 ) then return true end + if ( tab and next(tab) ) then return true end end action = function() local all = creds.Credentials:new(creds.ALL_DATA) - return (all and tostring(all) or nil) + return all:getTable() end