Library brute
The brute library is an attempt to create a common framework for performing password guessing against remote services.
The library currently attempts to parallelize the guessing by starting a number of working threads and increasing that number gradually until brute.threads limit is reached. The starting number of threads can be set with brute.start argument, it defaults to 5. The brute.threads argument defaults to 20. It is worth noticing that the number of working threads will grow exponentially until any error occurs, after that the engine will switch to linear growth.
The library contains the following classes:
Engine
Error
Options
In order to make use of the framework a script needs to implement a Driver class. The Driver class is then to be passed as a parameter to the Engine constructor, which creates a new instance for each guess. The Driver class SHOULD implement the following four methods:
Driver:login = function( self, username, password ) Driver:check = function( self ) Driver:connect = function( self ) Driver:disconnect = function( self )
The login
method does not need a lot of explanation. The login
function should return two parameters. If the login was successful it should
return true and a creds.Account
. If the login was a failure it
should return false and a brute.Error
. The driver can signal
the Engine to retry a set of credentials by calling the Error objects
setRetry
method. It may also signal the Engine to abort all
password guessing by calling the Error objects setAbort
method.
Finally, the driver can notify the Engine about protocol related exception
(like the ftp code 421 "Too many connections") by calling
setReduce
method. The latter will signal the Engine to reduce
the number of running worker threads.
The following example code demonstrates how the Error object can be used.
-- After a number of incorrect attempts VNC blocks us, so we abort if ( not(status) and x:match("Too many authentication failures") ) then local err = brute.Error:new( data ) -- signal the engine to abort err:setAbort( true ) return false, err elseif ( not(status) ) then local err = brute.Error:new( "VNC handshake failed" ) -- This might be temporary, signal the engine to retry err:setRetry( true ) return false, err end . . . -- Return a simple error, no retry needed return false, brute.Error:new( "Incorrect password" )
The purpose of the check
method is to be able to determine
whether the script has all the information it needs, before starting the
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.
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
a thread blocking, due to insufficient free sockets, after it has acquired a
username and password pair.
The following sample code illustrates how to implement a sample
Driver
that sends each username and password over a socket.
Driver = { new = function(self, host, port, options) local o = {} setmetatable(o, self) self.__index = self o.host = host o.port = port o.options = options return o end, connect = function( self ) self.socket = nmap.new_socket() return self.socket:connect( self.host, self.port ) end, disconnect = function( self ) return self.socket:close() end, check = function( self ) return true end, login = function( self, username, password ) local status, err, data status, err = self.socket:send( username .. ":" .. password) status, data = self.socket:receive_bytes(1) if ( data:match("SUCCESS") ) then return true, creds.Account:new(username, password, creds.State.VALID) end return false, brute.Error:new( "login failed" ) end, }
The following sample code illustrates how to pass the Driver
off to the brute engine.
action = function(host, port) local options = { key1 = val1, key2 = val2 } local status, accounts = brute.Engine:new(Driver, host, port, options):start() if( not(status) ) then return accounts end return stdnse.format_output( true, accounts ) end
The Engine is written with performance and reasonable resource usage in mind and requires minimum extra work from a script developer. A trivial approach is to spawn as many working threads as possible regardless of network conditions, other scripts' needs, and protocol response. This indeed works well, but only in ideal conditions. In reality there might be several scripts running or only limited number of threads are allowed to use sockets at any given moment (as it is in Nmap). A more intelligent approach is to automate the management of Engine's running threads, so that performance of other scripts does not suffer because of exhaustive brute force work. This can be done on three levels: protocol, network, and resource level.
On the protocol level the developer should notify the Engine about connection
restrictions imposed by a server that can be learned during a protocol
communication. Like code 421 "To many connections" is used in FTP. Reasonably
in such cases we would like to reduce the number of connections to this
service, hence saving resources for other work and reducing the load on the
target server. This can be done by returning an Error object with called
setReduce
method on it. The error will make the Engine reduce
the number of running threads.
Following is an example how it can be done for FTP brute.
local line = <response from the server> if(string.match(line, "^230")) then stdnse.debug1("Successful login: %s/%s", user, pass) return true, creds.Account:new( user, pass, creds.State.VALID) elseif(string.match(line, "^530")) then return false, brute.Error:new( "Incorrect password" ) elseif(string.match(line, "^421")) then local err = brute.Error:new("Too many connections") err:setReduce(true) return false, err elseif(string.match(line, "^220")) then elseif(string.match(line, "^331")) then else stdnse.debug1("WARNING: Unhandled response: %s", line) local err = brute.Error:new("Unhandled response") err:setRetry(true) return false, err end
On the network level we want to catch errors that can occur because of
network congestion or target machine specifics, say firewalled. These
errors can be caught as return results of operations on sockets, like
local status, err = socket.receive()
. Asking a developer to
relay such errors to the Engine is counterproductive, and it would lead to
bloated scripts with lots of repetitive code. The Engine takes care of that
with a little help from the developer. The only thing that needs to be
done is to use brute.new_socket()
instead of
nmap.new_socket()
when creating a socket in a script.
NOTE: A socket created with brute.new_socket()
will behave as
a regular socket when used without the brute library. The returned object
is a BruteSocket instance, which can be treated as a regular socket object.
Example on creating "brute" socket.
connect = function( self ) self.socket = brute.new_socket() local status, err = self.socket:connect(self.host, self.port) self.socket:set_timeout(arg_timeout) if(not(status)) then return false, brute.Error:new( "Couldn't connect to host: " .. err ) end return true end
On the resource level the Engine can query the current status of the NSE. As of the time of writing, the only parameter used is a number of threads waiting for connection (as was said before the NSE has a constraint on the number of concurrent connections due to performance reasons). With a running brute script the limit can be hit pretty fast, which can affect performance of other scripts. To mitigate this situation resource management strategy is used, and the Engine will reduce the number of working threads if there are any threads waiting for connection. As a result the preference for connection will be given to non brute scripts and if there are many brute scripts running simultaneously, then they will not exhaust resources unnecessarily. This feature is enabled by default and does not require any additional work from the developer.
Stagnation avoidance mechanism is implemented to alert users about services
that might have failed during bruteforcing. The Engine will abort if all working
threads have been experiencing connection errors during 100 consequentive
iterations of the main thread loop. If brute.killstagnated
is set to false
the Engine will continue after issuing a
warning.
For a complete example of a brute implementation consult the
svn-brute.nse
or vnc-brute.nse
scripts
Author:
Copyright © Same as Nmap--See https://nmap.org/book/man-legal.html
Source: https://svn.nmap.org/nmap/nselib/brute.lua
Script Arguments
- 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.
- brute.unique
make sure that each password is only guessed once (default: true)
- brute.retries
the number of times to retry if recoverable failures occur. (default: 2)
- brute.useraspass
guess the username as password for each user (default: true)
- brute.start
the number of threads the engine will start with. (default: 5).
- brute.threads
the number of initial worker threads, the number of active threads will be automatically adjusted.
- brute.credfile
a file containing username and password pairs delimited by '/'
- brute.emptypass
guess an empty password for each user (default: false)
- brute.guesses
the number of guesses to perform against each account. (default: 0 (unlimited)). The argument can be used to prevent account lockouts.
- brute.firstonly
stop guessing after first password is found (default: false)
- brute.delay
the number of seconds to wait between guesses (default: 0)
- brute.passonly
iterate over passwords only for services that provide only a password for authentication. (default: false)
Functions
- account_iterator (users, pass, mode)
Iterates over each user and password
- activeThreads (self)
Calculates the number of threads that are actually doing any work
- add (self, obj)
Adds new item to the vault (if possible)
- addWorker (self, cvar)
Adds new worker thread using start function
- checkBoolArg (arg, default)
Checks if a script argument is boolean true or false
- credential_iterator (f)
Credential iterator (for default or known user/pass combinations)
- doAuthenticate (self)
Does the actual authentication request
- get_next_credential (self)
Iterator wrapper used to iterate over all registered iterators
- getMessage (self)
Get the error message reported
- isAbort (self)
Was the error abortable
- isDone (self)
Is the thread done?
- isReduce (self)
Checks if the error signals to reduce the number of running threads
- isRetry (self)
Is the error recoverable?
- new (self, driver, host, port, options)
Creates a new Engine instance
- passwords_iterator ()
Default password iterator that uses unpwdb
- pw_same_as_user_iterator (users, case)
An iterator that returns the username as password
- pw_ucase_iterator (users, passwords, mode, pass)
An iterator that returns the username and uppercase password
- pw_user_iterator (users, pass)
Try each user for each password (password in outer loop)
- setAbort (self, b)
Set the error as abort all threads
- setDone (self, b)
Signals the engine that the thread is done and should be terminated
- setMaxThreads (self, max)
Limit the number of worker threads
- setMode (self, mode)
Sets the brute mode to either iterate over users or passwords
- setOption (self, param, value)
Sets an option parameter
- setPasswordIterator (self, passwordIterator)
Sets the password iterator
- setReduce (self, r)
Set the error as reduce the number of running threads
- setRetry (self, r)
Set the error as recoverable
- setTitle (self, title)
Set an alternate title for the result output (default: Accounts)
- setUsernameIterator (self, usernameIterator)
Sets the username iterator
- start (self)
Starts the brute-force
- threadCount (self)
Returns the number of non-dead threads
- user_pw_iterator (users, pass)
Try each password for each user (user in outer loop)
- usernames_iterator ()
Default username iterator that uses unpwdb
Functions
- account_iterator (users, pass, mode)
-
Iterates over each user and password
Parameters
- users
- table/function containing list of users
- pass
- table/function containing list of passwords
- mode
- string, should be either 'user' or 'pass' and controls whether the users or passwords are in the 'outer' loop
Return value:
function iterator - activeThreads (self)
-
Calculates the number of threads that are actually doing any work
Parameters
- self
Return value:
count number of threads performing activity - add (self, obj)
-
Adds new item to the vault (if possible)
Parameters
- self
- obj
Return value:
true if insert is successful, false if the vault is full - addWorker (self, cvar)
-
Adds new worker thread using start function
Parameters
- self
- cvar
Return value:
new thread object - checkBoolArg (arg, default)
-
Checks if a script argument is boolean true or false
Parameters
- arg
- string containing the name of the argument to check
- default
- boolean containing the default value
Return value:
boolean, true if argument evaluates to 1 or true, else false - credential_iterator (f)
-
Credential iterator (for default or known user/pass combinations)
Parameters
- f
- file handle to file containing credentials separated by '/'
Return value:
function iterator - doAuthenticate (self)
-
Does the actual authentication request
Parameters
- self
Return values:
- true on success, false on failure
- response Account on success, Error on failure
- get_next_credential (self)
-
Iterator wrapper used to iterate over all registered iterators
Parameters
- self
Return value:
iterator function - getMessage (self)
-
Get the error message reported
Parameters
- self
Return value:
msg string containing the error message - isAbort (self)
-
Was the error abortable
Parameters
- self
Return value:
status true if the driver flagged the engine to abort - isDone (self)
-
Is the thread done?
Parameters
- self
Return value:
status true if done, false if not - isReduce (self)
-
Checks if the error signals to reduce the number of running threads
Parameters
- self
Return value:
status true if reduce, false otherwise - isRetry (self)
-
Is the error recoverable?
Parameters
- self
Return value:
status true if the error is recoverable, false if not - new (self, driver, host, port, options)
-
Creates a new Engine instance
Parameters
- self
- driver
- host
- table as passed to the action method of the script
- port
- table as passed to the action method of the script
- options
- table containing any script specific options
Return value:
o new Engine instance - passwords_iterator ()
-
Default password iterator that uses unpwdb
- pw_same_as_user_iterator (users, case)
-
An iterator that returns the username as password
Parameters
- users
- function returning the next user
- case
- string [optional] 'upper' or 'lower', specifies if user and password pairs should be case converted.
Return value:
function iterator - pw_ucase_iterator (users, passwords, mode, pass)
-
An iterator that returns the username and uppercase password
Parameters
- users
- table containing list of users
- passwords
- mode
- string, should be either 'user' or 'pass' and controls whether the users or passwords are in the 'outer' loop
- pass
- table containing list of passwords
Return value:
function iterator - pw_user_iterator (users, pass)
-
Try each user for each password (password in outer loop)
Parameters
- users
- table/function containing list of users
- pass
- table/function containing list of passwords
Return value:
function iterator - setAbort (self, b)
-
Set the error as abort all threads
Parameters
- self
- b
- boolean true if the engine should abort guessing on all threads
- setDone (self, b)
-
Signals the engine that the thread is done and should be terminated
Parameters
- self
- b
- boolean true if done, unset or false if not
- setMaxThreads (self, max)
-
Limit the number of worker threads
Parameters
- self
- max
- number containing the maximum number of allowed threads
- setMode (self, mode)
-
Sets the brute mode to either iterate over users or passwords
Parameters
- self
- mode
- string containing either "user" or "password"
Return values:
- status true on success else false
- err string containing the error message on failure
See also:
- setOption (self, param, value)
-
Sets an option parameter
Parameters
- self
- param
- string containing the parameter name
- value
- string containing the parameter value
- setPasswordIterator (self, passwordIterator)
-
Sets the password iterator
Parameters
- self
- passwordIterator
- function to set as a password iterator
- setReduce (self, r)
-
Set the error as reduce the number of running threads
Parameters
- self
- r
- boolean true if should reduce, unset or false if not
- setRetry (self, r)
-
Set the error as recoverable
Parameters
- self
- r
- boolean true if the engine should attempt to retry the credentials, unset or false if not
- setTitle (self, title)
-
Set an alternate title for the result output (default: Accounts)
Parameters
- self
- title
- string containing the title value
- setUsernameIterator (self, usernameIterator)
-
Sets the username iterator
Parameters
- self
- usernameIterator
- function to set as a username iterator
- start (self)
-
Starts the brute-force
Parameters
- self
Return values:
- status true on success, false on failure
- err string containing error message on failure
- threadCount (self)
-
Returns the number of non-dead threads
Parameters
- self
Return value:
count number of non-dead threads - user_pw_iterator (users, pass)
-
Try each password for each user (user in outer loop)
Parameters
- users
- table/function containing list of users
- pass
- table/function containing list of passwords
Return value:
function iterator - usernames_iterator ()
-
Default username iterator that uses unpwdb