Index: nselib/brute.lua
===================================================================
--- nselib/brute.lua (revision 20030)
+++ nselib/brute.lua (arbetskopia)
@@ -64,8 +64,12 @@
-- brute force. It's the method where you should check, e.g., if the correct
-- database or repository URL was specified or not. On success, the
-- check method returns true, on failure it returns false and the
--- brute force engine aborts.
+-- brute force engine aborts.
--
+-- NOTE: The check method is deprecated and will be removed from
+-- all scripts in the future. Scripts should do this check in the action
+-- function instead.
+--
-- The connect method provides the framework with the ability to
-- ensure that the thread can run once it has been dispatched a set of
-- credentials. As the sockets in NSE are limited we want to limit the risk of
@@ -143,12 +147,24 @@
-- @args brute.delay the number of seconds to wait between guesses (default: 0)
-- @args brute.threads the number of initial worker threads, the number of
-- active threads will be automatically adjusted.
--- @args brute.mode can be user or pass and determines if passwords are guessed
--- against users (user) or users against passwords (pass).
--- (default: pass)
+-- @args brute.mode can be user, pass or creds and determines what mode to run
+-- the engine in.
+-- * user - the unpwdb library is used to guess passwords, every password
+-- password is tried for each user. (The user iterator is in the
+-- outer loop)
+-- * pass - the unpwdb library is used to guess passwords, each password
+-- is tried for every user. (The password iterator is in the
+-- outer loop)
+-- * creds- a set of credentials (username and password pairs) are
+-- guessed against the service. This allows for lists of known
+-- or common username and password combinations to be tested.
+-- If no mode is specified and the script has not added any custom
+-- iterator the pass mode will be enabled.
+-- @args brute.credfile a file containing username and password pairs delimited
+-- by '/'
--
--- Version 0.5
+-- Version 0.6
-- Created 06/12/2010 - v0.1 - created by Patrik Karlsson
-- Revised 07/13/2010 - v0.2 - added connect, disconnect methods to Driver
--
@@ -158,9 +174,12 @@
-- found" message.
-- Revised 08/14/2010 - v0.5 - added some documentation and smaller changes per
-- David's request.
+-- Revised 08/30/2010 - v0.6 - added support for custom iterators and did some
+-- needed cleanup.
module(... or "brute", package.seeall)
require 'unpwdb'
+require 'datafiles'
-- Options that can be set through --script-args
Options = {
@@ -350,11 +369,21 @@
o.threads = {}
o.counter = 0
o.max_threads = tonumber(nmap.registry.args["brute.threads"]) or 10
+ o.iterators = {}
o.error = nil
o.tps = {}
return o
end,
+ addIterator = function( self, iterator )
+ table.insert( self.iterators, iterator )
+ end,
+
+ setMode = function( self, mode )
+ mode = ( mode == "user" or mode == "creds" or mode == "pass" ) and mode or nil
+ assert(mode, ("Unsupported mode: (%s)"):format(mode))
+ end,
+
--- Limit the number of worker threads
--
-- @param max number containing the maximum number of allowed threads
@@ -392,6 +421,27 @@
return count
end,
+ --- Iterator wrapper used to iterate over all registered iterators
+ --
+ -- @return iterator function
+ get_next_credential = function( self )
+ local function next_credential ()
+ local used_creds = {}
+ -- iterate over all credential iterators
+ for _, iter in ipairs( self.iterators ) do
+ for user, pass in iter do
+ -- makes sure the credentials have not been tested before
+ if ( not(used_creds[user..pass]) ) then
+ used_creds[user..pass] = true
+ coroutine.yield( user, pass )
+ end
+ end
+ end
+ while true do coroutine.yield(nil, nil) end
+ end
+ return coroutine.wrap( next_credential )
+ end,
+
--- Does the actual authentication request
--
-- @return true on success, false on failure
@@ -403,6 +453,8 @@
local retries = self.options.max_retries
local msg
+ local next_credential = self:get_next_credential()
+
repeat
driver = self.driver:new( self.host, self.port, self.driver_options )
status = driver:connect()
@@ -411,7 +463,7 @@
if ( status ) then
if ( not(username) and not(password) ) then
- username, password = self.iterator()
+ username, password = next_credential()
end
-- make sure that all threads locked in connect stat terminate quickly
@@ -541,45 +593,62 @@
-- @return status true on success, false on failure
-- @return err string containing error message on failure
start = function(self)
- local status, usernames, passwords, response
+
local result, valid_accounts, stats = {}, {}, {}
local condvar = nmap.condvar( valid_accounts )
- local sum, tps, time_diff = 0, 0, 0
- -- check if the driver is ready!
- status, response = self.driver:new( self.host, self.port ):check()
- if( not(status) ) then
- return false, response
+ -- Only run the check method if it exist. We should phase this out
+ -- in favor of a check in the action function of the script
+ if ( self.driver:new( self.host, self.port ).check ) then
+ -- check if the driver is ready!
+ local status, response = self.driver:new( self.host, self.port ):check()
+ if( not(status) ) then return false, response end
end
- status, usernames = unpwdb.usernames()
- if ( not(status) ) then
- return false, "Failed to load usernames"
- end
+ local status, usernames = unpwdb.usernames()
+ if ( not(status) ) then return false, "Failed to load usernames" end
-- make sure we have a valid pw file
- status, passwords = unpwdb.passwords()
- if ( not(status) ) then
- return false, "Failed to load passwords"
- end
+ local status, passwords = unpwdb.passwords()
+ if ( not(status) ) then return false, "Failed to load passwords" end
+ local mode = stdnse.get_script_args("brute.mode")
+
-- Are we guessing against a service that has no username (eg. VNC)
if ( self.options.passonly ) then
local function single_user_iter(next)
- local function next_user()
- coroutine.yield( "" )
- end
+ local function next_user() coroutine.yield( "" ) end
return coroutine.wrap(next_user)
end
- self.iterator = Engine.usrpwd_iterator( self, single_user_iter(), passwords )
- elseif ( nmap.registry.args['brute.mode'] and nmap.registry.args['brute.mode'] == 'user' ) then
- self.iterator = Engine.usrpwd_iterator( self, usernames, passwords )
- elseif( nmap.registry.args['brute.mode'] and nmap.registry.args['brute.mode'] == 'pass' ) then
- self.iterator = Engine.pwdusr_iterator( self, usernames, passwords )
- elseif ( nmap.registry.args['brute.mode'] ) then
+ table.insert( self.iterators, Iterators.user_pw_iterator( single_user_iter(), passwords ) )
+ elseif ( mode == 'creds' ) then
+ local credfile = stdnse.get_script_args("brute.credfile")
+ if ( not(credfile) ) then
+ return false, "No credential file specified"
+ end
+
+ local f = io.open( credfile, "r" )
+ if ( not(f) ) then
+ return false, ("Failed to open credfile (%s)"):format(credfile)
+ end
+ local creds = {}
+ for line in f:lines() do
+ local trim = function(s) return s:match('^()%s*$') and '' or s:match('^%s*(.*%S)') end
+ line = trim(line)
+ local user, pass = line:match("^([^%/]*)%/(.*)$")
+ table.insert(creds, { [user]=pass } )
+ end
+
+ table.insert( self.iterators, Iterators.credential_iterator( creds ) )
+ elseif ( mode and mode == 'user' ) then
+ table.insert( self.iterators, Iterators.user_pw_iterator( usernames, passwords ) )
+ elseif( mode and mode == 'pass' ) then
+ table.insert( self.iterators, Iterators.pw_user_iterator( usernames, passwords ) )
+ elseif ( mode ) then
return false, ("Unsupported mode: %s"):format(nmap.registry.args['brute.mode'])
- else
- self.iterator = Engine.pwdusr_iterator( self, usernames, passwords )
+ -- Default to the pw_user_iterator in case no iterator was specified
+ elseif ( 0 == #self.iterators ) then
+ table.insert( self.iterators, Iterators.pw_user_iterator( usernames, passwords ) )
end
self.starttime = os.time()
@@ -605,16 +674,11 @@
end
-- calculate the average tps
- for _, v in ipairs( self.tps ) do
- sum = sum + v
- end
- time_diff = ( os.time() - self.starttime )
- if ( time_diff == 0 ) then time_diff = 1 end
- if ( sum == 0 ) then
- tps = self.counter / time_diff
- else
- tps = sum / #self.tps
- end
+ local sum = 0
+ for _, v in ipairs( self.tps ) do sum = sum + v end
+ local time_diff = ( os.time() - self.starttime )
+ time_diff = ( time_diff == 0 ) and 1 or time_diff
+ local tps = ( sum == 0 ) and ( self.counter / time_diff ) or ( sum / #self.tps )
-- Add the statistics to the result
table.insert(stats, ("Perfomed %d guesses in %d seconds, average tps: %d"):format( self.counter, time_diff, tps ) )
@@ -636,86 +700,140 @@
return true, result
end,
- --- Credential iterator, tries every user for each password
+}
+
+Iterators = {
+
+ --- Iterates over each user and password
--
- -- @param usernames iterator from unpwdb
- -- @param passwords iterator from unpwdb
- -- @return username string
- -- @return password string
- pwdusr_iterator = function(self, usernames, passwords)
- local function next_password_username ()
- local tested_creds = {}
+ -- @param users table containing list of users
+ -- @param pass table containing list of passwords
+ -- @param mode string, should be either 'user' or 'pass' and controls
+ -- whether the users or passwords are in the 'outer' loop
+ -- @return function iterator
+ account_iterator = function(users, pass, mode)
+ local function next_credential ()
+ local outer, inner
+ if ( mode == 'pass' ) then
+ outer = pass; inner = users
+ elseif ( mode == 'user' ) then
+ outer = users; inner = pass
+ else
+ return
+ end
- -- should we check for same password as username
- if ( self.options.user_as_password ) then
- for username in usernames do
- if ( not( tested_creds[username] ) ) then
- tested_creds[username] = {}
+ if ( 'table' == type(users) and 'table' == type(pass) ) then
+ for _, o in ipairs(outer) do
+ for _, i in ipairs(inner) do
+ if ( mode == 'pass' ) then
+ coroutine.yield( i, o )
+ else
+ coroutine.yield( o, i )
+ end
end
-
- tested_creds[username][username] = true
- if ( not(self.found_accounts) or not(self.found_accounts[username]) ) then
- coroutine.yield(username, username)
- end
end
- end
- usernames("reset")
-
- for password in passwords do
- for username in usernames do
- if ( not(tested_creds[username]) ) then
- tested_creds[username] = {}
- end
- if ( self.options.check_unique and not(tested_creds[username][password]) ) then
- tested_creds[username][password] = true
- if ( not(self.found_accounts) or not(self.found_accounts[username]) ) then
- coroutine.yield(username, password)
+ elseif ( 'function' == type(users) and 'function' == type(pass) ) then
+ for o in outer do
+ for i in inner do
+ if ( mode == 'pass' ) then
+ coroutine.yield( i, o )
+ else
+ coroutine.yield( o, i )
end
end
+ inner("reset")
end
- usernames("reset")
end
while true do coroutine.yield(nil, nil) end
end
- return coroutine.wrap(next_password_username)
+ return coroutine.wrap( next_credential )
end,
-
- --- Credential iterator, tries every password for each user
+
+ --- Try each password for each user (user in outer loop)
--
- -- @param usernames iterator from unpwdb
- -- @param passwords iterator from unpwdb
- -- @return username string
- -- @return password string
- usrpwd_iterator = function(self, usernames, passwords)
- local function next_username_password ()
- local tested_creds = {}
+ -- @param users table containing list of users
+ -- @param pass table containing list of passwords
+ -- @return function iterator
+ user_pw_iterator = function( users, pass )
+ return Iterators.account_iterator( users, pass, "user" )
+ end,
- for username in usernames do
- -- set's up a table to track tested credentials
- tested_creds[username] = {}
-
- -- should we check for same password as username
- if ( self.options.user_as_password and not(self.options.passonly) ) then
- tested_creds[username][username:lower()] = true
- if ( not(self.found_accounts) or not(self.found_accounts[username]) ) then
- coroutine.yield(username, username:lower())
- end
- end
-
- for password in passwords do
- if ( self.options.check_unique and not(tested_creds[username][password]) ) then
- tested_creds[username][password] = true
- if ( not(self.found_accounts) or not(self.found_accounts[username]) ) then
- coroutine.yield(username, password)
- end
- end
- end
- passwords("reset")
+ --- Try each user for each password (password in outer loop)
+ --
+ -- @param users table containing list of users
+ -- @param pass table containing list of passwords
+ -- @return function iterator
+ pw_user_iterator = function( users, pass )
+ return Iterators.account_iterator( users, pass, "pass" )
+ end,
+
+ --- An iterator that returns the username as password
+ --
+ -- @param users table containing list of users
+ -- @param case string [optional] 'upper' or 'lower', specifies if user
+ -- and password pairs should be case converted.
+ -- @return function iterator
+ pw_same_as_user_iterator = function( users, case )
+ local function next_credential ()
+ for _, user in ipairs(users) do
+ if ( case == 'upper' ) then
+ coroutine.yield( user:upper(), user:upper() )
+ elseif( case == 'lower' ) then
+ coroutine.yield( user:lower(), user:lower() )
+ else
+ coroutine.yield( user, user )
+ end
end
while true do coroutine.yield(nil, nil) end
end
- return coroutine.wrap(next_username_password)
+ return coroutine.wrap( next_credential )
end,
+ --- An iterator that returns the username and uppercase password
+ --
+ -- @param users table containing list of users
+ -- @param pass table containing list of passwords
+ -- @param mode string, should be either 'user' or 'pass' and controls
+ -- whether the users or passwords are in the 'outer' loop
+ -- @return function iterator
+ pw_ucase_iterator = function( users, passwords, mode )
+ local function next_credential ()
+ for user, pass in Iterators.account_iterator(users, passwords, mode) do
+ coroutine.yield( user, pass:upper() )
+ end
+ while true do coroutine.yield(nil, nil) end
+ end
+ return coroutine.wrap( next_credential )
+ end,
+
+ --- Credential iterator (for default or known user/pass combinations)
+ --
+ -- @param creds table containing username/pass combinations
+ -- the table should be of the following format
+ -- { ["user"] = "pass", ["user2"] = "pass2" }
+ -- @return function iterator
+ credential_iterator = function( creds )
+ local function next_credential ()
+ for _, item in ipairs(creds) do
+ for user, pass in pairs(item) do
+ coroutine.yield( user, pass )
+ end
+ end
+ while true do coroutine.yield( nil, nil ) end
+ end
+ return coroutine.wrap( next_credential )
+ end,
+
+ unpwdb_iterator = function( mode )
+ local status, users, passwords
+
+ status, users = unpwdb.usernames()
+ if ( not(status) ) then return end
+
+ status, passwords = unpwdb.passwords()
+ if ( not(status) ) then return end
+
+ return Iterators.account_iterator( users, passwords, mode )
+ end,
+
}
-