PageRenderTime 47ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/nselib/brute.lua

https://github.com/prakashgamit/nmap
Lua | 940 lines | 543 code | 69 blank | 328 comment | 40 complexity | 5c8eeb485d5cfd575b4ee168b2128f96 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, LGPL-2.0, LGPL-2.1
  1. ---
  2. -- The brute library is an attempt to create a common framework for performing
  3. -- password guessing against remote services.
  4. --
  5. -- The library currently attempts to parallellize the guessing by starting
  6. -- a number of working threads. The number of threads can be defined using
  7. -- the brute.threads argument, it defaults to 10.
  8. --
  9. -- The library contains the following classes:
  10. -- * <code>Account</code>
  11. -- ** Implements a simple account class, that converts account "states" to common text representation.
  12. -- ** The state can be either of the following: OPEN, LOCKED or DISABLED
  13. -- * <code>Engine</code>
  14. -- ** The actual engine doing the brute-forcing .
  15. -- * <code>Error</code>
  16. -- ** Class used to return errors back to the engine.
  17. -- * <code>Options</code>
  18. -- ** Stores any options that should be used during brute-forcing.
  19. --
  20. -- In order to make use of the framework a script needs to implement a Driver
  21. -- class. The Driver class is then to be passed as a parameter to the Engine
  22. -- constructor, which creates a new instance for each guess. The Driver class
  23. -- SHOULD implement the following four methods:
  24. --
  25. -- <code>
  26. -- Driver:login = function( self, username, password )
  27. -- Driver:check = function( self )
  28. -- Driver:connect = function( self )
  29. -- Driver:disconnect = function( self )
  30. -- </code>
  31. --
  32. -- The <code>login</code> method does not need a lot of explanation. The login
  33. -- function should return two parameters. If the login was successful it should
  34. -- return true and an <code>Account</code>. If the login was a failure it
  35. -- should return false and an <code>Error</code>. The driver can signal the
  36. -- Engine to retry a set of credentials by calling the Error objects
  37. -- <code>setRetry</code> method. It may also signal the Engine to abort all
  38. -- password guessing by calling the Error objects <code>setAbort</code> method.
  39. --
  40. -- The following example code demonstrates how the Error object can be used.
  41. --
  42. -- <code>
  43. -- -- After a number of incorrect attempts VNC blocks us, so we abort
  44. -- if ( not(status) and x:match("Too many authentication failures") ) then
  45. -- local err = brute.Error:new( data )
  46. -- -- signal the engine to abort
  47. -- err:setAbort( true )
  48. -- return false, err
  49. -- elseif ( not(status) ) then
  50. -- local err = brute.Error:new( "VNC handshake failed" )
  51. -- -- This might be temporary, signal the engine to retry
  52. -- err:setRetry( true )
  53. -- return false, err
  54. -- end
  55. -- .
  56. -- .
  57. -- .
  58. -- -- Return a simple error, no retry needed
  59. -- return false, brute.Error:new( "Incorrect password" )
  60. -- </code>
  61. --
  62. -- The purpose of the <code>check</code> method is to be able to determine
  63. -- whether the script has all the information it needs, before starting the
  64. -- brute force. It's the method where you should check, e.g., if the correct
  65. -- database or repository URL was specified or not. On success, the
  66. -- <code>check</code> method returns true, on failure it returns false and the
  67. -- brute force engine aborts.
  68. --
  69. -- NOTE: The <code>check</code> method is deprecated and will be removed from
  70. -- all scripts in the future. Scripts should do this check in the action
  71. -- function instead.
  72. --
  73. -- The <code>connect</code> method provides the framework with the ability to
  74. -- ensure that the thread can run once it has been dispatched a set of
  75. -- credentials. As the sockets in NSE are limited we want to limit the risk of
  76. -- a thread blocking, due to insufficient free sockets, after it has aquired a
  77. -- username and password pair.
  78. --
  79. -- The following sample code illustrates how to implement a sample
  80. -- <code>Driver</code> that sends each username and password over a socket.
  81. --
  82. -- <code>
  83. -- Driver = {
  84. -- new = function(self, host, port, options)
  85. -- local o = {}
  86. -- setmetatable(o, self)
  87. -- self.__index = self
  88. -- o.host = host
  89. -- o.port = port
  90. -- o.options = options
  91. -- return o
  92. -- end,
  93. -- connect = function( self )
  94. -- self.socket = nmap.new_socket()
  95. -- return self.socket:connect( self.host, self.port )
  96. -- end,
  97. -- disconnect = function( self )
  98. -- return self.socket:close()
  99. -- end,
  100. -- check = function( self )
  101. -- return true
  102. -- end,
  103. -- login = function( self, username, password )
  104. -- local status, err, data
  105. -- status, err = self.socket:send( username .. ":" .. password)
  106. -- status, data = self.socket:receive_bytes(1)
  107. --
  108. -- if ( data:match("SUCCESS") ) then
  109. -- return true, brute.Account:new(username, password, "OPEN")
  110. -- end
  111. -- return false, brute.Error:new( "login failed" )
  112. -- end,
  113. -- }
  114. -- </code>
  115. --
  116. -- The following sample code illustrates how to pass the <code>Driver</code>
  117. -- off to the brute engine.
  118. --
  119. -- <code>
  120. -- action = function(host, port)
  121. -- local options = { key1 = val1, key2 = val2 }
  122. -- local status, accounts = brute.Engine:new(Driver, host, port, options):start()
  123. -- if( not(status) ) then
  124. -- return accounts
  125. -- end
  126. -- return stdnse.format_output( true, accounts )
  127. -- end
  128. -- </code>
  129. --
  130. -- For a complete example of a brute implementation consult the
  131. -- <code>svn-brute.nse</code> or <code>vnc-brute.nse</code> scripts
  132. --
  133. -- @args brute.useraspass guess the username as password for each user
  134. -- (default: true)
  135. -- @args brute.emptypass guess an empty password for each user
  136. -- (default: false)
  137. -- @args brute.unique make sure that each password is only guessed once
  138. -- (default: true)
  139. -- @args brute.firstonly stop guessing after first password is found
  140. -- (default: false)
  141. -- @args brute.passonly iterate over passwords only for services that provide
  142. -- only a password for authentication. (default: false)
  143. -- @args brute.retries the number of times to retry if recoverable failures
  144. -- occure. (default: 3)
  145. -- @args brute.delay the number of seconds to wait between guesses (default: 0)
  146. -- @args brute.threads the number of initial worker threads, the number of
  147. -- active threads will be automatically adjusted.
  148. -- @args brute.mode can be user, pass or creds and determines what mode to run
  149. -- the engine in.
  150. -- * user - the unpwdb library is used to guess passwords, every password
  151. -- password is tried for each user. (The user iterator is in the
  152. -- outer loop)
  153. -- * pass - the unpwdb library is used to guess passwords, each password
  154. -- is tried for every user. (The password iterator is in the
  155. -- outer loop)
  156. -- * creds- a set of credentials (username and password pairs) are
  157. -- guessed against the service. This allows for lists of known
  158. -- or common username and password combinations to be tested.
  159. -- If no mode is specified and the script has not added any custom
  160. -- iterator the pass mode will be enabled.
  161. -- @args brute.credfile a file containing username and password pairs delimited
  162. -- by '/'
  163. -- @args brute.guesses the number of guesses to perform against each account.
  164. -- (default: 0 (unlimited)). The argument can be used to prevent account
  165. -- lockouts.
  166. --
  167. -- @author "Patrik Karlsson <patrik@cqure.net>"
  168. -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
  169. --
  170. -- Version 0.73
  171. -- Created 06/12/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
  172. -- Revised 07/13/2010 - v0.2 - added connect, disconnect methods to Driver
  173. -- <patrik@cqure.net>
  174. -- Revised 07/21/2010 - v0.3 - documented missing argument brute.mode
  175. -- Revised 07/23/2010 - v0.4 - fixed incorrect statistics and changed output to
  176. -- include statistics, and to display "no accounts
  177. -- found" message.
  178. -- Revised 08/14/2010 - v0.5 - added some documentation and smaller changes per
  179. -- David's request.
  180. -- Revised 08/30/2010 - v0.6 - added support for custom iterators and did some
  181. -- needed cleanup.
  182. -- Revised 06/19/2011 - v0.7 - added support for creds library [Patrik]
  183. -- Revised 07/07/2011 - v0.71- fixed some minor bugs, and changed credential
  184. -- iterator to use a file handle instead of table
  185. -- Revised 07/21/2011 - v0.72- added code to allow script reporting invalid
  186. -- (non existing) accounts using setInvalidAccount
  187. -- Revised 11/12/2011 - v0.73- added support for max guesses per account to
  188. -- prevent account lockouts.
  189. -- bugfix: added support for guessing the username
  190. -- as password per default, as suggested by the
  191. -- documentation.
  192. local coroutine = require "coroutine"
  193. local creds = require "creds"
  194. local io = require "io"
  195. local nmap = require "nmap"
  196. local os = require "os"
  197. local stdnse = require "stdnse"
  198. local table = require "table"
  199. local unpwdb = require "unpwdb"
  200. _ENV = stdnse.module("brute", stdnse.seeall)
  201. -- Engine options that can be set by scripts
  202. -- Supported options are:
  203. -- * firstonly - stop after finding the first correct password
  204. -- (can be set using script-arg brute.firstonly)
  205. -- * passonly - guess passwords only, don't supply a username
  206. -- (can be set using script-arg brute.passonly)
  207. -- * max_retries - the amount of retries to do before aborting
  208. -- (can be set using script-arg brute.retries)
  209. -- * delay - sets the delay between attempts
  210. -- (can be set using script-arg brute.delay)
  211. -- * mode - can be set to either cred, user or pass and controls
  212. -- whether the engine should iterate over users, passwords
  213. -- or fetch a list of credentials from a single file.
  214. -- (can be set using script-arg brut.mode)
  215. -- * title - changes the title of the result table where the
  216. -- passwords are returned.
  217. -- * nostore - don't store the results in the credential library
  218. -- * max_guesses - the maximum amount of guesses to perform for each
  219. -- account.
  220. -- * useraspass - guesses the username as password (default: true)
  221. -- * emptypass - guesses an empty string as password (default: false)
  222. --
  223. Options = {
  224. new = function(self)
  225. local o = {}
  226. setmetatable(o, self)
  227. self.__index = self
  228. o.emptypass = self.checkBoolArg("brute.emptypass", false)
  229. o.useraspass = self.checkBoolArg("brute.useraspass", true)
  230. o.firstonly = self.checkBoolArg("brute.firstonly", false)
  231. o.passonly = self.checkBoolArg("brute.passonly", false)
  232. o.max_retries = tonumber( nmap.registry.args["brute.retries"] ) or 3
  233. o.delay = tonumber( nmap.registry.args["brute.delay"] ) or 0
  234. o.max_guesses = tonumber( nmap.registry.args["brute.guesses"] ) or 0
  235. return o
  236. end,
  237. --- Checks if a script argument is boolean true or false
  238. --
  239. -- @param arg string containing the name of the argument to check
  240. -- @param default boolean containing the default value
  241. -- @return boolean, true if argument evaluates to 1 or true, else false
  242. checkBoolArg = function( arg, default )
  243. local val = stdnse.get_script_args(arg) or default
  244. return (val == "true" or val==true or tonumber(val)==1)
  245. end,
  246. --- Sets the brute mode to either iterate over users or passwords
  247. -- @see description for more information.
  248. --
  249. -- @param mode string containing either "user" or "password"
  250. -- @return status true on success else false
  251. -- @return err string containing the error message on failure
  252. setMode = function( self, mode )
  253. local modes = { "password", "user", "creds" }
  254. local supported = false
  255. for _, m in ipairs(modes) do
  256. if ( mode == m ) then supported = true end
  257. end
  258. if ( not(supported) ) then
  259. stdnse.print_debug("ERROR: brute.options.setMode: mode %s not supported", mode)
  260. return false, "Unsupported mode"
  261. else
  262. self.mode = mode
  263. end
  264. return true
  265. end,
  266. --- Sets an option parameter
  267. --
  268. -- @param param string containing the parameter name
  269. -- @param value string containing the parameter value
  270. setOption = function( self, param, value ) self[param] = value end,
  271. --- Set an alternate title for the result output (default: Accounts)
  272. --
  273. -- @param title string containing the title value
  274. setTitle = function(self, title) self.title = title end,
  275. }
  276. -- The account object which is to be reported back from each driver
  277. Account =
  278. {
  279. --- Creates a new instance of the Account class
  280. --
  281. -- @param username containing the user's name
  282. -- @param password containing the user's password
  283. -- @param state containing the account state and should be one of the
  284. -- following <code>OPEN</code>, <code>LOCKED</code>,
  285. -- <code>DISABLED</code>.
  286. new = function(self, username, password, state)
  287. local o = { username = username, password = password, state = state }
  288. setmetatable(o, self)
  289. self.__index = self
  290. return o
  291. end,
  292. --- Converts an account object to a printable script
  293. --
  294. -- @return string representation of object
  295. toString = function( self )
  296. local c
  297. if ( #self.username > 0 ) then
  298. c = ("%s:%s"):format( self.username, #self.password > 0 and self.password or "<empty>" )
  299. else
  300. c = ("%s"):format( ( self.password and #self.password > 0 ) and self.password or "<empty>" )
  301. end
  302. if ( creds.StateMsg[self.state] ) then
  303. return ( "%s - %s"):format(c, creds.StateMsg[self.state] )
  304. else
  305. return ("%s"):format(c)
  306. end
  307. end,
  308. }
  309. -- The Error class, is currently only used to flag for retries
  310. -- It also contains the error message, if one was returned from the driver.
  311. Error =
  312. {
  313. retry = false,
  314. new = function(self, msg)
  315. local o = { msg = msg, done = false }
  316. setmetatable(o, self)
  317. self.__index = self
  318. return o
  319. end,
  320. --- Is the error recoverable?
  321. --
  322. -- @return status true if the error is recoverable, false if not
  323. isRetry = function( self ) return self.retry end,
  324. --- Set the error as recoverable
  325. --
  326. -- @param r boolean true if the engine should attempt to retry the
  327. -- credentials, unset or false if not
  328. setRetry = function( self, r ) self.retry = r end,
  329. --- Set the error as abort all threads
  330. --
  331. -- @param b boolean true if the engine should abort guessing on all threads
  332. setAbort = function( self, b ) self.abort = b end,
  333. --- Was the error abortable
  334. --
  335. -- @return status true if the driver flagged the engine to abort
  336. isAbort = function( self ) return self.abort end,
  337. --- Get the error message reported
  338. --
  339. -- @return msg string containing the error message
  340. getMessage = function( self ) return self.msg end,
  341. --- Is the thread done?
  342. --
  343. -- @return status true if done, false if not
  344. isDone = function( self ) return self.done end,
  345. --- Signals the engine that the thread is done and should be terminated
  346. --
  347. -- @param b boolean true if done, unset or false if not
  348. setDone = function( self, b ) self.done = b end,
  349. -- Marks the username as invalid, aborting further guessing.
  350. -- @param username
  351. setInvalidAccount = function(self, username)
  352. self.invalid_account = username
  353. end,
  354. -- Checks if the error reported the account as invalid.
  355. -- @return username string containing the invalid account
  356. isInvalidAccount = function(self)
  357. return self.invalid_account
  358. end,
  359. }
  360. -- The brute engine, doing all the nasty work
  361. Engine =
  362. {
  363. STAT_INTERVAL = 20,
  364. --- Creates a new Engine instance
  365. --
  366. -- @param driver, the driver class that should be instantiated
  367. -- @param host table as passed to the action method of the script
  368. -- @param port table as passed to the action method of the script
  369. -- @param options table containing any script specific options
  370. -- @return o new Engine instance
  371. new = function(self, driver, host, port, options)
  372. local o = {
  373. driver = driver,
  374. host = host,
  375. port = port,
  376. driver_options = options,
  377. terminate_all = false,
  378. error = nil,
  379. counter = 0,
  380. threads = {},
  381. tps = {},
  382. iterator = nil ,
  383. usernames = usernames_iterator(),
  384. passwords = passwords_iterator(),
  385. found_accounts = {},
  386. account_guesses = {},
  387. options = Options:new(),
  388. }
  389. setmetatable(o, self)
  390. self.__index = self
  391. o.max_threads = stdnse.get_script_args("brute.threads") or 10
  392. return o
  393. end,
  394. --- Sets the username iterator
  395. --
  396. -- @param usernameIterator function to set as a username iterator
  397. setUsernameIterator = function(self,usernameIterator)
  398. self.usernames = usernameIterator
  399. end,
  400. --- Sets the password iterator
  401. --
  402. -- @param passwordIterator function to set as a password iterator
  403. setPasswordIterator = function(self,passwordIterator)
  404. self.passwords = passwordIterator
  405. end,
  406. --- Limit the number of worker threads
  407. --
  408. -- @param max number containing the maximum number of allowed threads
  409. setMaxThreads = function( self, max ) self.max_threads = max end,
  410. --- Returns the number of non-dead threads
  411. --
  412. -- @return count number of non-dead threads
  413. threadCount = function( self )
  414. local count = 0
  415. for thread in pairs(self.threads) do
  416. if ( coroutine.status(thread) == "dead" ) then
  417. self.threads[thread] = nil
  418. else
  419. count = count + 1
  420. end
  421. end
  422. return count
  423. end,
  424. --- Calculates the number of threads that are actually doing any work
  425. --
  426. -- @return count number of threads performing activity
  427. activeThreads = function( self )
  428. local count = 0
  429. for thread, v in pairs(self.threads) do
  430. if ( v.guesses ~= nil ) then count = count + 1 end
  431. end
  432. return count
  433. end,
  434. --- Iterator wrapper used to iterate over all registered iterators
  435. --
  436. -- @return iterator function
  437. get_next_credential = function( self )
  438. local function next_credential ()
  439. for user, pass in self.iterator do
  440. -- makes sure the credentials have not been tested before
  441. self.used_creds = self.used_creds or {}
  442. pass = pass or "nil"
  443. if ( not(self.used_creds[user..pass]) ) then
  444. self.used_creds[user..pass] = true
  445. coroutine.yield( user, pass )
  446. end
  447. end
  448. while true do coroutine.yield(nil, nil) end
  449. end
  450. return coroutine.wrap( next_credential )
  451. end,
  452. --- Does the actual authentication request
  453. --
  454. -- @return true on success, false on failure
  455. -- @return response Account on success, Error on failure
  456. doAuthenticate = function( self )
  457. local status, response
  458. local next_credential = self:get_next_credential()
  459. local retries = self.options.max_retries
  460. local username, password
  461. repeat
  462. local driver = self.driver:new( self.host, self.port, self.driver_options )
  463. status = driver:connect()
  464. -- Did we succesfully connect?
  465. if ( status ) then
  466. if ( not(username) and not(password) ) then
  467. repeat
  468. username, password = next_credential()
  469. if ( not(username) and not(password) ) then
  470. driver:disconnect()
  471. self.threads[coroutine.running()].terminate = true
  472. return false
  473. end
  474. until ( ( not(self.found_accounts) or not(self.found_accounts[username]) ) and
  475. ( self.options.max_guesses == 0 or not(self.account_guesses[username]) or
  476. self.options.max_guesses > self.account_guesses[username] ) )
  477. -- increases the number of guesses for an account
  478. self.account_guesses[username] = self.account_guesses[username] and self.account_guesses[username] + 1 or 1
  479. end
  480. -- make sure that all threads locked in connect stat terminate quickly
  481. if ( Engine.terminate_all ) then
  482. driver:disconnect()
  483. return false
  484. end
  485. local c
  486. -- Do we have a username or not?
  487. if ( username and #username > 0 ) then
  488. c = ("%s/%s"):format(username, #password > 0 and password or "<empty>")
  489. else
  490. c = ("%s"):format(#password > 0 and password or "<empty>")
  491. end
  492. local msg = ( retries ~= self.options.max_retries ) and "Re-trying" or "Trying"
  493. stdnse.print_debug(2, "%s %s against %s:%d", msg, c, self.host.ip, self.port.number )
  494. status, response = driver:login( username, password )
  495. driver:disconnect()
  496. driver = nil
  497. end
  498. retries = retries - 1
  499. -- End if:
  500. -- * The guess was successfull
  501. -- * The response was not set to retry
  502. -- * We've reached the maximum retry attempts
  503. until( status or ( response and not( response:isRetry() ) ) or retries == 0)
  504. -- Increase the amount of total guesses
  505. self.counter = self.counter + 1
  506. -- did we exhaust all retries, terminate and report?
  507. if ( retries == 0 ) then
  508. Engine.terminate_all = true
  509. self.error = "Too many retries, aborted ..."
  510. response = Error:new("Too many retries, aborted ...")
  511. response.abort = true
  512. end
  513. return status, response
  514. end,
  515. login = function(self, cvar )
  516. local condvar = nmap.condvar( cvar )
  517. local thread_data = self.threads[coroutine.running()]
  518. local interval_start = os.time()
  519. while( true ) do
  520. -- Should we terminate all threads?
  521. if ( self.terminate_all or thread_data.terminate ) then break end
  522. local status, response = self:doAuthenticate()
  523. if ( status ) then
  524. -- Prevent locked accounts from appearing several times
  525. if ( not(self.found_accounts) or self.found_accounts[response.username] == nil ) then
  526. if ( not(self.options.nostore) ) then
  527. creds.Credentials:new( self.options.script_name, self.host, self.port ):add(response.username, response.password, response.state )
  528. else
  529. self.credstore = self.credstore or {}
  530. table.insert(self.credstore, response:toString() )
  531. end
  532. stdnse.print_debug("Discovered account: %s", response:toString())
  533. -- if we're running in passonly mode, and want to continue guessing
  534. -- we will have a problem as the username is always the same.
  535. -- in this case we don't log the account as found.
  536. if ( not(self.options.passonly) ) then
  537. self.found_accounts[response.username] = true
  538. end
  539. -- Check if firstonly option was set, if so abort all threads
  540. if ( self.options.firstonly ) then self.terminate_all = true end
  541. end
  542. else
  543. if ( response and response:isAbort() ) then
  544. self.terminate_all = true
  545. self.error = response:getMessage()
  546. break
  547. elseif( response and response:isDone() ) then
  548. break
  549. elseif ( response and response:isInvalidAccount() ) then
  550. self.found_accounts[response:isInvalidAccount()] = true
  551. end
  552. end
  553. local timediff = (os.time() - interval_start)
  554. -- This thread made another guess
  555. thread_data.guesses = ( thread_data.guesses and thread_data.guesses + 1 or 1 )
  556. -- Dump statistics at regular intervals
  557. if ( timediff > Engine.STAT_INTERVAL ) then
  558. interval_start = os.time()
  559. local tps = self.counter / ( os.time() - self.starttime )
  560. table.insert(self.tps, tps )
  561. stdnse.print_debug(2, "threads=%d,tps=%d", self:activeThreads(), tps )
  562. end
  563. -- if delay was speciefied, do sleep
  564. if ( self.options.delay > 0 ) then stdnse.sleep( self.options.delay ) end
  565. end
  566. condvar "signal"
  567. end,
  568. --- Starts the brute-force
  569. --
  570. -- @return status true on success, false on failure
  571. -- @return err string containing error message on failure
  572. start = function(self)
  573. local cvar = {}
  574. local condvar = nmap.condvar( cvar )
  575. assert(self.options.script_name, "SCRIPT_NAME was not set in options.script_name")
  576. assert(self.port.number and self.port.protocol, "Invalid port table detected")
  577. self.port.service = self.port.service or "unknown"
  578. -- Only run the check method if it exist. We should phase this out
  579. -- in favor of a check in the action function of the script
  580. if ( self.driver:new( self.host, self.port, self.driver_options ).check ) then
  581. -- check if the driver is ready!
  582. local status, response = self.driver:new( self.host, self.port, self.driver_options ):check()
  583. if( not(status) ) then return false, response end
  584. end
  585. local usernames = self.usernames
  586. local passwords = self.passwords
  587. if ( "function" ~= type(usernames) ) then
  588. return false, "Invalid usernames iterator"
  589. end
  590. if ( "function" ~= type(passwords) ) then
  591. return false, "Invalid passwords iterator"
  592. end
  593. local mode = self.options.mode or stdnse.get_script_args("brute.mode")
  594. -- if no mode was given, but a credfile is present, assume creds mode
  595. if ( not(mode) and stdnse.get_script_args("brute.credfile") ) then
  596. if ( stdnse.get_script_args("userdb") or
  597. stdnse.get_script_args("passdb") ) then
  598. return false, "\n ERROR: brute.credfile can't be used in combination with userdb/passdb"
  599. end
  600. mode = 'creds'
  601. end
  602. -- Are we guessing against a service that has no username (eg. VNC)
  603. if ( self.options.passonly ) then
  604. local function single_user_iter(next)
  605. local function next_user() coroutine.yield( "" ) end
  606. return coroutine.wrap(next_user)
  607. end
  608. -- only add this iterator if no other iterator was specified
  609. if self.iterator == nil then
  610. self.iterator = Iterators.user_pw_iterator( single_user_iter(), passwords )
  611. end
  612. elseif ( mode == 'creds' ) then
  613. local credfile = stdnse.get_script_args("brute.credfile")
  614. if ( not(credfile) ) then
  615. return false, "No credential file specified (see brute.credfile)"
  616. end
  617. local f = io.open( credfile, "r" )
  618. if ( not(f) ) then
  619. return false, ("Failed to open credfile (%s)"):format(credfile)
  620. end
  621. self.iterator = Iterators.credential_iterator( f )
  622. elseif ( mode and mode == 'user' ) then
  623. self.iterator = self.iterator or Iterators.user_pw_iterator( usernames, passwords )
  624. elseif( mode and mode == 'pass' ) then
  625. self.iterator = self.iterator or Iterators.pw_user_iterator( usernames, passwords )
  626. elseif ( mode ) then
  627. return false, ("Unsupported mode: %s"):format(mode)
  628. -- Default to the pw_user_iterator in case no iterator was specified
  629. elseif ( self.iterator == nil ) then
  630. self.iterator = Iterators.pw_user_iterator( usernames, passwords )
  631. end
  632. if ( ( not(mode) or mode == 'user' or mode == 'pass' ) and self.options.useraspass ) then
  633. -- if we're only guessing passwords, this doesn't make sense
  634. if ( not(self.options.passonly) ) then
  635. self.iterator = unpwdb.concat_iterators(Iterators.pw_same_as_user_iterator(usernames, "lower"),self.iterator)
  636. end
  637. end
  638. if ( ( not(mode) or mode == 'user' or mode == 'pass' ) and self.options.emptypass ) then
  639. local function empty_pass_iter()
  640. local function next_pass()
  641. coroutine.yield( "" )
  642. end
  643. return coroutine.wrap(next_pass)
  644. end
  645. self.iterator = Iterators.account_iterator(usernames, empty_pass_iter(), mode or "pass")
  646. end
  647. self.starttime = os.time()
  648. -- Startup all worker threads
  649. for i=1, self.max_threads do
  650. local co = stdnse.new_thread( self.login, self, cvar )
  651. self.threads[co] = {}
  652. self.threads[co].running = true
  653. end
  654. -- wait for all threads to finnish running
  655. while self:threadCount()>0 do condvar "wait" end
  656. local valid_accounts
  657. if ( not(self.options.nostore) ) then
  658. valid_accounts = creds.Credentials:new(self.options.script_name, self.host, self.port):getTable()
  659. else
  660. valid_accounts = self.credstore
  661. end
  662. local result = {}
  663. -- Did we find any accounts, if so, do formatting
  664. if ( valid_accounts and #valid_accounts > 0 ) then
  665. valid_accounts.name = self.options.title or "Accounts"
  666. table.insert( result, valid_accounts )
  667. else
  668. table.insert( result, {"No valid accounts found", name="Accounts"} )
  669. end
  670. -- calculate the average tps
  671. local sum = 0
  672. for _, v in ipairs( self.tps ) do sum = sum + v end
  673. local time_diff = ( os.time() - self.starttime )
  674. time_diff = ( time_diff == 0 ) and 1 or time_diff
  675. local tps = ( sum == 0 ) and ( self.counter / time_diff ) or ( sum / #self.tps )
  676. -- Add the statistics to the result
  677. local stats = {}
  678. table.insert(stats, ("Performed %d guesses in %d seconds, average tps: %d"):format( self.counter, time_diff, tps ) )
  679. stats.name = "Statistics"
  680. table.insert( result, stats )
  681. if ( self.options.max_guesses > 0 ) then
  682. -- we only display a warning if the guesses are equal to max_guesses
  683. for user, guesses in pairs(self.account_guesses) do
  684. if ( guesses == self.options.max_guesses ) then
  685. table.insert( result, { name = "Information",
  686. ("Guesses restricted to %d tries per account to avoid lockout"):format(self.options.max_guesses) } )
  687. break
  688. end
  689. end
  690. end
  691. result = ( #result ) and stdnse.format_output( true, result ) or ""
  692. -- Did any error occure? If so add this to the result.
  693. if ( self.error ) then
  694. result = result .. (" \n ERROR: %s"):format( self.error )
  695. return false, result
  696. end
  697. return true, result
  698. end,
  699. }
  700. --- Default username iterator that uses unpwdb
  701. --
  702. usernames_iterator = function()
  703. local status, usernames = unpwdb.usernames()
  704. if ( not(status) ) then return "Failed to load usernames" end
  705. return usernames
  706. end
  707. --- Default password iterator that uses unpwdb
  708. --
  709. passwords_iterator = function()
  710. local status, passwords = unpwdb.passwords()
  711. if ( not(status) ) then return "Failed to load passwords" end
  712. return passwords
  713. end
  714. Iterators = {
  715. --- Iterates over each user and password
  716. --
  717. -- @param users table/function containing list of users
  718. -- @param pass table/function containing list of passwords
  719. -- @param mode string, should be either 'user' or 'pass' and controls
  720. -- whether the users or passwords are in the 'outer' loop
  721. -- @return function iterator
  722. account_iterator = function(users, pass, mode)
  723. local function next_credential ()
  724. local outer, inner
  725. if "table" == type(users) then
  726. users = unpwdb.table_iterator(users)
  727. end
  728. if "table" == type(pass) then
  729. pass = unpwdb.table_iterator(pass)
  730. end
  731. if ( mode == 'pass' ) then
  732. outer, inner = pass, users
  733. elseif ( mode == 'user' ) then
  734. outer, inner = users, pass
  735. else
  736. return
  737. end
  738. for o in outer do
  739. for i in inner do
  740. if ( mode == 'pass' ) then
  741. coroutine.yield( i, o )
  742. else
  743. coroutine.yield( o, i )
  744. end
  745. end
  746. inner("reset")
  747. end
  748. while true do coroutine.yield(nil, nil) end
  749. end
  750. return coroutine.wrap( next_credential )
  751. end,
  752. --- Try each password for each user (user in outer loop)
  753. --
  754. -- @param users table/function containing list of users
  755. -- @param pass table/function containing list of passwords
  756. -- @return function iterator
  757. user_pw_iterator = function( users, pass )
  758. return Iterators.account_iterator( users, pass, "user" )
  759. end,
  760. --- Try each user for each password (password in outer loop)
  761. --
  762. -- @param users table/function containing list of users
  763. -- @param pass table/function containing list of passwords
  764. -- @return function iterator
  765. pw_user_iterator = function( users, pass )
  766. return Iterators.account_iterator( users, pass, "pass" )
  767. end,
  768. --- An iterator that returns the username as password
  769. --
  770. -- @param users function returning the next user
  771. -- @param case string [optional] 'upper' or 'lower', specifies if user
  772. -- and password pairs should be case converted.
  773. -- @return function iterator
  774. pw_same_as_user_iterator = function( users, case )
  775. local function next_credential ()
  776. for user in users do
  777. if ( case == 'upper' ) then
  778. coroutine.yield(user, user:upper())
  779. elseif( case == 'lower' ) then
  780. coroutine.yield(user, user:lower())
  781. else
  782. coroutine.yield(user, user)
  783. end
  784. end
  785. users("reset")
  786. while true do coroutine.yield(nil, nil) end
  787. end
  788. return coroutine.wrap( next_credential )
  789. end,
  790. --- An iterator that returns the username and uppercase password
  791. --
  792. -- @param users table containing list of users
  793. -- @param pass table containing list of passwords
  794. -- @param mode string, should be either 'user' or 'pass' and controls
  795. -- whether the users or passwords are in the 'outer' loop
  796. -- @return function iterator
  797. pw_ucase_iterator = function( users, passwords, mode )
  798. local function next_credential ()
  799. for user, pass in Iterators.account_iterator(users, passwords, mode) do
  800. coroutine.yield( user, pass:upper() )
  801. end
  802. while true do coroutine.yield(nil, nil) end
  803. end
  804. return coroutine.wrap( next_credential )
  805. end,
  806. --- Credential iterator (for default or known user/pass combinations)
  807. --
  808. -- @param f file handle to file containing credentials separated by '/'
  809. -- @return function iterator
  810. credential_iterator = function( f )
  811. local function next_credential ()
  812. local c = {}
  813. for line in f:lines() do
  814. if ( not(line:match("^#!comment:")) ) then
  815. local trim = function(s) return s:match('^()%s*$') and '' or s:match('^%s*(.*%S)') end
  816. line = trim(line)
  817. local user, pass = line:match("^([^%/]*)%/(.*)$")
  818. coroutine.yield( user, pass )
  819. end
  820. end
  821. f:close()
  822. while true do coroutine.yield( nil, nil ) end
  823. end
  824. return coroutine.wrap( next_credential )
  825. end,
  826. unpwdb_iterator = function( mode )
  827. local status, users, passwords
  828. status, users = unpwdb.usernames()
  829. if ( not(status) ) then return end
  830. status, passwords = unpwdb.passwords()
  831. if ( not(status) ) then return end
  832. return Iterators.account_iterator( users, passwords, mode )
  833. end,
  834. }
  835. return _ENV;