1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-29 10:59:02 +00:00

XML structured output for brute.lua and creds.lua

The @xmloutput section documentation is not done, and I'm not sure how
to best do it, since it will be the same for all brute.lua scripts. This
is how it looks:

metasploit-msgrpc-brute:
<table key="Accounts">
  <table>
    <elem key="username">root</elem>
    <elem key="state">Valid credentials</elem>
    <elem key="password">root</elem>
  </table>
</table>
<elem key="Statistics">Performed 3 guesses in 4 seconds, average tps: 0</elem>

creds-summary:
<table key="127.0.0.1">
  <table key="9929/nping-echo">
    <table>
      <elem key="password">123456</elem>
      <elem key="state">Valid credentials</elem>
    </table>
  </table>
  <table key="55553/unknown">
    <table>
      <elem key="username">root</elem>
      <elem key="state">Valid credentials</elem>
      <elem key="password">root</elem>
    </table>
  </table>
</table>
This commit is contained in:
dmiller
2014-09-23 05:23:19 +00:00
parent aaf7838b10
commit e42409be93
3 changed files with 70 additions and 62 deletions

View File

@@ -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

View File

@@ -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<b end)
table.insert( host_tbl, svc_tbl )
table.sort( svc_tbl )
end
-- sort the services
table.sort( host_tbl,
function(a,b)
return tonumber(a.name:match("^(%d+)")) < tonumber(b.name:match("^(%d+)"))
end
)
table.insert( output, host_tbl )
setmetatable(host_tbl, {
__pairs = sorted_pairs( function(a,b)
return tonumber(a:match("^(%d+)")) < tonumber(b:match("^(%d+)"))
end )
})
end
-- sort the IP addresses
table.sort( output, function(a, b) return ipOps.compare_ip(a.name, "le", b.name) end )
if ( self.host and self.port and #output > 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 <code>stdnse.format_output</code>
__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,
}

View File

@@ -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