home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Networking / nmap-5.00-setup.exe / scripts / smb-brute.nse < prev    next >
Text File  |  2009-07-06  |  41KB  |  1,046 lines

  1. description = [[
  2. Attempts to guess username/password combinations over SMB, storing discovered combinations 
  3. for use in other scripts. Every attempt will be made to get a valid list of users and to 
  4. verify each username before actually using them. When a username is discovered, besides 
  5. being printed, it is also saved in the Nmap registry so other Nmap scripts can use it. That
  6. means that if you're going to run smb-brute.nse, you should run other smb scripts you want. 
  7. This checks passwords in a case-insensitive way, determining case after a password is found, 
  8. for Windows versions before Vista. 
  9.  
  10. This script is specifically targeted towards security auditors or penetration testers. 
  11. One example of its use, suggested by Brandon Enright, was hooking up smb-brute.nse to the
  12. database of usernames and passwords used by the Conficker worm (the password list can be
  13. found here, among other places <http://www.skullsecurity.org/wiki/index.php/Passwords>. 
  14. Then, the network is scanned and all systems that would be infected by Conficker are 
  15. discovered. 
  16.  
  17. From the penetration tester perspective its use is pretty obvious. By discovering weak passwords
  18. on SMB, a protocol that's well suited for bruteforcing, access to a system can be gained. 
  19. Further, passwords discovered against Windows with SMB might also be used on Linux or MySQL
  20. or custom Web applications. Discovering a password greatly beneficial for a pen-tester. 
  21.  
  22. This script uses a lot of little tricks that I (Ron Bowes) describe in detail in a blog 
  23. posting <http://www.skullsecurity.org/blog/?p=164>. The tricks will be summarized here, but
  24. that blog is the best place to learn more. 
  25.  
  26. Usernames and passwords are initially taken from the unpw library. If possible, the usernames
  27. are verified as existing by taking advantage of Windows' odd behaviour with invalid username
  28. and invalid password responses. As soon as it is able, this script will download a full list 
  29. of usernames from the server and replace the unpw usernames with those. This enables the 
  30. script to restrict itself to actual accounts only. 
  31.  
  32. When an account is discovered, it's saved in the <code>smb</code> module (which uses the Nmap
  33. registry). If an account is already saved, the account's privileges are checked; accounts 
  34. with administrator privileges are kept over accounts without. The specific method for checking
  35. is by calling GetShareInfo("IPC$"), which requires administrative privileges. Once this script
  36. is finished (since it's runlevel 0.5, it'll run first), other scripts will use the saved account
  37. to perform their checks. 
  38.  
  39. The blank password is always tried first, followed by "special passwords" (such as the username
  40. and the username reversed). Once those are exhausted, the unpw password list is used. 
  41.  
  42. One major goal of this script is to avoid accout lockouts. This is done in a few ways. First, 
  43. when a lockout is detected, unless you user specifically overrides it with the <code>smblockout</code>
  44. argument, the scan stops. Second, all usernames are checked with the most common passwords first, 
  45. so with not-too-strict lockouts (10 invalid attempts), the 10 most common passwords will still 
  46. be tried. Third, one account, called the canary, 'goes out ahead' -- that is, three invalid 
  47. attempts are made (by default) to ensure that it's locked out before others are. 
  48.  
  49. In addition to active accounts, this script will identify valid passwords for accounts that
  50. are disabled, guest-equivalent, and require password changes. Although these accounts can't
  51. be used, it's good to know that the password is valid. In other cases, it's impossible to
  52. tell a valid password (if an account is locked out, for example). These are displayed, too. 
  53. Certain accounts, such as guest or some guest-equivalent, will permit any password. This
  54. is also detected. When possible, the SMB protocol is used to its fullest to get maximum 
  55. information. 
  56.  
  57. When possible, checks are done using a case-insensitive password, then proper case is
  58. determined with a fairly efficient bruteforce. For example, if the actual password is 
  59. 'PassWord', then 'password' will work and 'PassWord' will be found afterwards (on the
  60. 14th attempt out of a possible 256 attempts, with the current algorithm). 
  61. ]]
  62. ---
  63. --@usage
  64. -- nmap --script smb-brute.nse -p445 <host>
  65. -- sudo nmap -sU -sS --script smb-brute.nse -p U:137,T:139 <host>
  66. --
  67. --@output
  68. -- Host script results:
  69. -- |  smb-brute:
  70. -- |  bad name:test => Login was successful
  71. -- |  consoletest:test => Password was correct, but user can't log in without changing it
  72. -- |  guest:<anything> => Password was correct, but user's account is disabled
  73. -- |  mixcase:BuTTeRfLY1 => Login was successful
  74. -- |  test:password1 => Login was successful
  75. -- |  this:password => Login was successful
  76. -- |  thisisaverylong:password => Login was successful
  77. -- |  thisisaverylongname:password => Login was successful
  78. -- |  thisisaverylongnamev:password => Login was successful
  79. -- |_ web:TeSt => Password was correct, but user's account is disabled
  80. -- 
  81. -- @args smblockout Unless this is set to '1' or 'true', the script won't continue if it 
  82. --       locks out an account or thinks it will lock out an account. 
  83. -- @args brutelimit Limits the number of usernames checked in the script. In some domains, 
  84. --       it's possible to end up with 10,000+ usernames on each server. By default, this
  85. --       will be 5000, which should be higher than most servers and also prevent infinite
  86. --       loops or other weird things. This will only affect the user list pulled from the
  87. --       server, not the username list. 
  88. -- @args canaries Sets the number of tests to do to attempt to lock out the first account. 
  89. --       This will lock out the first account without locking out the rest of the accounts. 
  90. --       The default is 3, which will only trigger strict lockouts, but will also bump the
  91. --       canary account up far enough to detect a lockout well before other accounts are
  92. --       hit. 
  93. -----------------------------------------------------------------------
  94.  
  95.  
  96. author = "Ron Bowes <ron@skullsecurity.net>"
  97. license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
  98. -- Set the runlevel to <1 to ensure that it runs before other scripts
  99. runlevel = 0.5
  100.  
  101. categories = {"intrusive", "auth"}
  102.  
  103. require 'msrpc'
  104. require 'smb'
  105. require 'stdnse'
  106. require 'unpwdb'
  107.  
  108. ---The maximum number of usernames to check (can be modified with smblimit argument)
  109. -- The limit exists because domains may have hundreds of thousands of accounts,
  110. -- potentially. 
  111. local LIMIT = 5000
  112.  
  113. hostrule = function(host)
  114.     return smb.get_port(host) ~= nil
  115. end
  116.  
  117. ---The possible result codes. These are simplified from the actual codes that SMB returns. 
  118. local results =
  119. {
  120.     SUCCESS            = 1, -- Login was successful
  121.     GUEST_ACCESS       = 2, -- Login was successful, but was granted guest access
  122.     NOT_GRANTED        = 3, -- Password was correct, but user wasn't allowed to log in (often happens with blank passwords)
  123.     DISABLED           = 4, -- Password was correct, but user's account is disabled
  124.     EXPIRED            = 5, -- Password was correct, but user's account is expired
  125.     CHANGE_PASSWORD    = 6, -- Password was correct, but user can't log in without changing it
  126.     ACCOUNT_LOCKED     = 7, -- User's account is locked out (hopefully not by us!)
  127.     ACCOUNT_LOCKED_NOW = 8, -- User's account just became locked out (oops!)
  128.     FAIL               = 9  -- User's password was incorrect
  129. }
  130.  
  131. ---Strings for debugging output
  132. local result_short_strings = {}
  133. result_short_strings[results.SUCCESS]            = "SUCCESS"
  134. result_short_strings[results.GUEST_ACCESS]       = "GUEST_ACCESS"
  135. result_short_strings[results.NOT_GRANTED]        = "NOT_GRANTED"
  136. result_short_strings[results.DISABLED]           = "DISABLED"
  137. result_short_strings[results.EXPIRED]            = "EXPIRED"
  138. result_short_strings[results.CHANGE_PASSWORD]    = "CHANGE_PASSWORD"
  139. result_short_strings[results.ACCOUNT_LOCKED]     = "LOCKED"
  140. result_short_strings[results.ACCOUNT_LOCKED_NOW] = "LOCKED_NOW"
  141. result_short_strings[results.FAIL]               = "FAIL"
  142.  
  143. ---The strings that the user will see
  144. local result_strings = {}
  145. result_strings[results.SUCCESS]            = "Login was successful"
  146. result_strings[results.GUEST_ACCESS]       = "Login was successful, but was granted guest access"
  147. result_strings[results.NOT_GRANTED]        = "Password was correct, but user wasn't allowed to log in (often happens with blank passwords)"
  148. result_strings[results.DISABLED]           = "Password was correct, but user's account is disabled"
  149. result_strings[results.EXPIRED]            = "Password was correct, but user's account is expired"
  150. result_strings[results.CHANGE_PASSWORD]    = "Password was correct, but user can't log in without changing it"
  151. result_strings[results.ACCOUNT_LOCKED]     = "User's account is locked out (hopefully not by us!)"
  152. result_strings[results.ACCOUNT_LOCKED_NOW] = "User's account just became locked out (oops!)"
  153. result_strings[results.FAIL]               = "User's password was incorrect"
  154.  
  155. ---Constants for special passwords. These each contain a null character, which is illegal in 
  156. -- actual passwords. 
  157. local USERNAME          = string.char(0) .. "username"
  158. local USERNAME_REVERSED = string.char(0) .. "username reversed"
  159. local special_passwords = { USERNAME, USERNAME_REVERSED }
  160.  
  161. ---Generates a random string of the requested length. This can be used to check how hosts react to 
  162. -- weird username/password combinations. 
  163. --@param length (optional) The length of the string to return. Default: 8. 
  164. --@param set    (optional) The set of letters to choose from. Default: upper, lower, numbers, and underscore. 
  165. --@return The random string. 
  166. local function get_random_string(length, set)
  167.     if(length == nil) then
  168.         length = 8
  169.     end
  170.  
  171.     if(set == nil) then
  172.         set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"
  173.     end
  174.  
  175.     local str = ""
  176.  
  177.     -- Seed the random number, if we haven't already
  178.     if(random_set == false) then
  179.         math.randomseed(os.time())
  180.         random_set = true
  181.     end
  182.  
  183.     for i = 1, length, 1 do
  184.         local random = math.random(#set)
  185.         str = str .. string.sub(set, random, random)
  186.     end
  187.  
  188.     return str
  189. end
  190.  
  191. ---Splits a string in the form "domain\user" into domain and user. 
  192. --@param str The string to split
  193. --@return (domain, username) The domain and the username. If no domain was given, nil is returned
  194. --        for domain. 
  195. local function split_domain(str)
  196.     local username, domain
  197.     local split = stdnse.strsplit("\\", str)
  198.  
  199.     if(#split > 1) then
  200.         domain = split[1]
  201.         username = split[2]
  202.     else
  203.         domain   = nil
  204.         username = str
  205.     end
  206.  
  207.     return domain, username
  208. end
  209.  
  210. ---Formats a username/password pair with an optional result. Just a way to keep things consistent 
  211. -- throughout the program. Currently, the format is "username:password => result". 
  212. --@param username The username. 
  213. --@param password [optional] The password. Default: "<unknown>". 
  214. --@param result   [optional] The result, as a constant. Default: not used. 
  215. --@return A string representing the input values. 
  216. local function format_result(username, password, result)
  217.  
  218.     if(username == "") then
  219.         username = "<blank>"
  220.     end
  221.  
  222.     if(password == nil) then
  223.         password = "<unknown>"
  224.     elseif(password == "") then
  225.         password = "<blank>"
  226.     end
  227.  
  228.     if(result == nil) then
  229.         return string.format("%s:%s", username, password)
  230.     else
  231.         return string.format("%s:%s => %s", username, password, result_strings[result])
  232.     end
  233. end
  234.  
  235. ---Decides which login type to use (lanman, ntlm, or other). Designed to keep things consistent. 
  236. --@param hostinfo The hostinfo table. 
  237. --@return A string representing the login type to use (that can be passed to SMB functions). 
  238. local function get_type(hostinfo)
  239.     -- Check if the user requested a specific type
  240.     if(nmap.registry.args.smbtype ~= nil) then
  241.         return nmap.registry.args.smbtype
  242.     end
  243.  
  244.     -- Otherwise, base the type on the operating system (TODO: other versions of Windows (7, 2008))
  245.     -- 2k8 example: "Windows Server (R) 2008 Datacenter without Hyper-V 6001 Service Pack 1"
  246.     if(string.find(string.lower(hostinfo['os']), "vista") ~= nil) then
  247.         return "ntlm"
  248.     elseif(string.find(string.lower(hostinfo['os']), "2008") ~= nil) then
  249.         return "ntlm"
  250.     elseif(string.find(string.lower(hostinfo['os']), "Windows 7") ~= nil) then
  251.         return "ntlm"
  252.     end
  253.  
  254.     return "lm"
  255. end
  256.  
  257. ---Stops the session, if one exists. This can be called as frequently as needed, it'll just return if no
  258. -- session is present, but it should generally be paired with a <code>restart_session</code> call. 
  259. --@param hostinfo The hostinfo table. 
  260. --@return (status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. 
  261. local function stop_session(hostinfo)
  262.     local status, err
  263.  
  264.     if(hostinfo['smbstate'] ~= nil) then
  265.         stdnse.print_debug(2, "smb-brute: Stopping the SMB session")
  266.         status, err = smb.stop(hostinfo['smbstate'])
  267.         if(status == false) then
  268.             return false, err
  269.         end
  270.  
  271.         hostinfo['smbstate'] = nil
  272.     end
  273.  
  274.  
  275.     return true
  276. end
  277.  
  278. ---Starts or restarts a SMB session with the host. Although this will automatically stop a session if
  279. -- one exists, it's a little cleaner to pair this with a <code>stop_session</code> call. 
  280. --@param hostinfo The hostinfo table. 
  281. --@return (status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. 
  282. local function restart_session(hostinfo)
  283.     local status, err, smbstate
  284.  
  285.     -- Stop the old session, if it exists
  286.     stop_session(hostinfo)
  287.  
  288.     stdnse.print_debug(2, "smb-brute: Starting the SMB session")
  289.     status, smbstate = smb.start_ex(hostinfo['host'], true, nil, nil, nil, true)
  290.     if(status == false) then
  291.         return false, smbstate
  292.     end
  293.  
  294.     hostinfo['smbstate'] = smbstate
  295.  
  296.     return true
  297. end
  298.  
  299. ---Attempts to log into an account, returning one of the <code>results</code> constants. Will always return to the 
  300. -- state where another login can be attempted. Will also differentiate between a hash and a password, and choose the 
  301. -- proper login method (unless overridden). Will interpret the result as much as possible. 
  302. --
  303. -- The session has to be active (ie, <code>restart_session</code> has to be called) before calling this function. 
  304. --
  305. --@param hostinfo The hostinfo table. 
  306. --@param username The username to try. 
  307. --@param password The password to try. 
  308. --@param logintype [optional] The logintype to use. Default: <code>get_type</code> is called. If <code>password</code>
  309. --       is a hash, this is ignored. 
  310. --@return Result, an integer value from the <code>results</code> constants. 
  311. local function check_login(hostinfo, username, password, logintype)
  312.     local result
  313.     local domain
  314.     local smbstate = hostinfo['smbstate']
  315.     if(logintype == nil) then
  316.         logintype = get_type(hostinfo)
  317.     end
  318. --io.write(string.format("Trying %s:%s\n", username, password))
  319.     -- Determine if we have a password hash or a password
  320.     if(#password == 32 or #password == 64 or #password == 65) then
  321. --io.write("Hash\n")
  322.         -- It's a hash (note: we always use NTLM hashes)
  323.         status, err      = smb.start_session(smbstate, username, domain, nil, password, "ntlm", false, true)
  324.     else
  325.         status, err      = smb.start_session(smbstate, username, domain, password, nil, logintype, false, false)
  326.     end
  327.    
  328.     if(status == true) then
  329.         if(smbstate['is_guest'] == 1) then
  330.             result = results.GUEST_ACCESS
  331.         else
  332.             result = results.SUCCESS
  333.         end
  334.  
  335.         smb.logoff(smbstate)
  336.     else
  337.         if(err == "NT_STATUS_LOGON_TYPE_NOT_GRANTED") then
  338.             result = results.NOT_GRANTED
  339.         elseif(err == "NT_STATUS_ACCOUNT_LOCKED_OUT") then
  340.             result = results.ACCOUNT_LOCKED
  341.         elseif(err == "NT_STATUS_ACCOUNT_DISABLED") then
  342.             result = results.DISABLED
  343.         elseif(err == "NT_STATUS_PASSWORD_MUST_CHANGE") then
  344.             result = results.CHANGE_PASSWORD
  345.         else
  346.             result = results.FAIL
  347.         end
  348.     end
  349.  
  350. --io.write(string.format("Result: %s\n\n", result_strings[result]))
  351.  
  352.     return result
  353. end
  354.  
  355. ---Determines whether or not a login was successful, based on what's known about the server's settings. This 
  356. -- is fairly straight forward, but has a couple little tricks. 
  357. --
  358. --@param hostinfo The hostinfo table. 
  359. --@param result   The result code. 
  360. --@return <code>true</code> if the password used for logging in was correct, <code>false</code> otherwise. Keep
  361. --        in mind that this doesn't imply the login was successful (only results.SUCCESS indicates that), rather
  362. --        that the password was valid. 
  363.  
  364. function is_positive_result(hostinfo, result)
  365.     -- If result is a FAIL, it's always bad
  366.     if(result == results.FAIL) then
  367.         return false
  368.     end
  369.  
  370.     -- If result matches what we discovered for invalid passwords, it's always bad
  371.     if(result == hostinfo['invalid_password']) then
  372.         return false
  373.     end
  374.  
  375.     -- If result was ACCOUNT_LOCKED, it's always bad (locked accounts should already be taken care of, but this
  376.     -- makes the function a bit more generic)
  377.     if(result == results.ACCOUNT_LOCKED) then
  378.         return false
  379.     end
  380.  
  381.     -- Otherwise, it's good
  382.     return true
  383. end
  384.  
  385. ---Count the number of one bits in a binary representation of the given number. This is used for case-sensitive
  386. -- checks. 
  387. --
  388. --@param num The number to count the ones for. 
  389. --@return The number of ones in the number
  390. local function count_ones(num)
  391.     local count = 0
  392.  
  393.     while num ~= 0 do
  394.         if(bit.band(num, 1) == 1) then
  395.             count = count + 1
  396.         end
  397.         num = bit.rshift(num, 1)
  398.     end
  399.  
  400.     return count
  401. end
  402.  
  403. ---Converts a string's case based on a binary number. For every '1' bit, the character is uppercased, and for every '0'
  404. -- bit it's lowercased. For example, "test" and 8 (1000) becomes "Test", while "test" and 11 (1011) becomes "TeST". 
  405. --
  406. --@param str The string to convert.
  407. --@param num The binary number representing the case. This value isn't checked, so if it's too large it's truncated, and if it's
  408. --           too small it's effectively zero-padded. 
  409. --@return The converted string. 
  410. local function convert_case(str, num)
  411.     local pos = #str
  412.  
  413.     -- Don't bother with blank strings (we probably won't get here anyway, but it doesn't hurt)
  414.     if(str == "") then
  415.         return ""
  416.     end
  417.  
  418.     while(num ~= 0) do
  419.         -- Check if the bit we're at is '1'
  420.         if(bit.band(num, 1) == 1) then
  421.             -- Check if we're at the beginning or end (or both) of the string -- those are special cases
  422.             if(pos == #str and pos == 1) then
  423.                 str = string.upper(string.sub(str, pos, pos))
  424.             elseif(pos == #str) then
  425.                 str = string.sub(str, 1, pos - 1) .. string.upper(string.sub(str, pos, pos))
  426.             elseif(pos == 1) then
  427.                 str = string.upper(string.sub(str, pos, pos)) .. string.sub(str, pos + 1, #str)
  428.             else
  429.                 str = string.sub(str, 1, pos - 1) .. string.upper(string.sub(str, pos, pos)) .. string.sub(str, pos + 1, #str)
  430.             end
  431.         end
  432.  
  433.         num = bit.rshift(num, 1)
  434.  
  435.         pos = pos - 1
  436.     end
  437.  
  438.     return str
  439. end
  440.  
  441. ---Attempts to determine the case of a password. This is done by trying every possible combination of upper and lowercase
  442. -- characters in the password, in the most efficient possible ordering, until the corerct case is found. 
  443. --
  444. -- A session has to be active when this function is called. 
  445. --
  446. --@param hostinfo The hostinfo table. 
  447. --@param username The username. 
  448. --@param password The password (it's assumed that it's all lowercase already, but it doesn't matter)
  449. --@return The password with the proper case, or the original password if it couldn't be determined (either the proper
  450. --        case wasn't found or the login type is incorrect). 
  451. local function find_password_case(hostinfo, username, password)
  452.     -- Only do this if we're using lanman, otherwise we already have the proper password
  453.     if(get_type(hostinfo) ~= "lm") then
  454.         return password
  455.     end
  456.  
  457.     -- Figure out how many possibilities exist
  458.     local max = math.pow(2, #password) - 1
  459.  
  460.     -- Create an array of them, starting with all the values whose binary representation has no ones, then one one, then two ones, etc.
  461.     local ordered = {}
  462.  
  463.     -- Cheat a bit, by adding all lower then all upper right at the start
  464.     ordered = {0, max}
  465.  
  466.     -- Loop backwards from the length of the password to 0. At each spot, put all numbers that have that many '1' bits
  467.     for i = 1, #password - 1, 1 do
  468.         for j = max, 0, -1 do
  469.             if(count_ones(j) == i) then
  470.                 table.insert(ordered, j)
  471.             end
  472.         end
  473.     end
  474.  
  475.     -- Create the list of converted passwords
  476.     for i = 1, #ordered, 1 do
  477.         local thispassword = convert_case(password, ordered[i])
  478.  
  479.         -- We specify "ntlm" for the login type because it's case sensitive
  480.         local result = check_login(hostinfo, username, thispassword, 'ntlm')
  481.         if(is_positive_result(hostinfo, result)) then
  482.             return thispassword
  483.         end
  484.     end
  485.  
  486.     -- Print an error message
  487.     stdnse.print_debug(1, "ERROR: smb-brute: Was unable to determine case of %s's password", username)
  488.  
  489.     -- If all else fails, just return the actual password (we probably shouldn't get here)
  490.     return password
  491. end
  492.  
  493. ---Initializes and returns the hostinfo table. This includes queuing up the username and password lists, determining
  494. -- the server's operating system,  and checking the server's response for invalid usernames/invalid passwords. 
  495. --
  496. --@param host The host object. 
  497. local function initialize(host)
  498.     local os, result
  499.     local hostinfo = {}
  500.     hostinfo['host'] = host
  501.     hostinfo['invalid_usernames'] = {}
  502.     hostinfo['locked_usernames'] = {}
  503.     hostinfo['accounts'] = {}
  504.     hostinfo['special_password'] = 1
  505.  
  506.     -- Get the OS (identifying windows versions tells us which hash to use)
  507.     result, os = smb.get_os(host)
  508.     if(result == false) then
  509.         hostinfo['os'] = "<Unknown>"
  510.     else
  511.         hostinfo['os'] = os['os']
  512.     end
  513.     stdnse.print_debug(1, "smb-brute: Remote operating system: %s", hostinfo['os'])
  514.  
  515.     -- Attempt to enumerate users
  516.     stdnse.print_debug(1, "smb-brute: Trying to get user list from server")
  517.     hostinfo['have_user_list'], _, hostinfo['user_list'] = msrpc.get_user_list(host)
  518.     hostinfo['user_list_index'] = 1
  519.     if(hostinfo['have_user_list'] and #hostinfo['user_list'] == 0) then
  520.         hostinfo['have_user_list'] = false
  521.     end
  522.  
  523.     -- If the enumeration failed, try using the built-in list
  524.     if(not(hostinfo['have_user_list'])) then
  525.         stdnse.print_debug(1, "smb-brute: Couldn't enumerate users (normal for Windows XP and higher), using unpwdb initially")
  526.         status, hostinfo['user_list_default'] = unpwdb.usernames()
  527.         if(status == false) then
  528.             return false, "Couldn't open username file"
  529.         end
  530.     end
  531.  
  532.     -- Open the password file
  533.     stdnse.print_debug(1, "smb-brute: Opening password list")
  534.     status, hostinfo['password_list'] = unpwdb.passwords()
  535.     if(status == false) then
  536.         return false, "Couldn't open password file"
  537.     end
  538.  
  539.     -- Start the SMB session
  540.     stdnse.print_debug(1, "smb-brute: Starting the initial SMB session")
  541.     status, err = restart_session(hostinfo)
  542.     if(status == false) then
  543.         stop_session(hostinfo)
  544.         return false, err
  545.     end
  546.  
  547.     -- Some hosts will accept any username -- check for this by trying to log in with a totally random name. If the 
  548.     -- server accepts it, it'll be impossible to bruteforce; if it gives us a weird result code, we have to remember
  549.     -- it. 
  550.     hostinfo['invalid_username'] = check_login(hostinfo, get_random_string(8), get_random_string(8), "ntlm")
  551.     hostinfo['invalid_password'] = check_login(hostinfo, "Administrator",      get_random_string(8), "ntlm")
  552.  
  553.     stdnse.print_debug(1, "smb-brute: Server's response to invalid usernames: %s", result_short_strings[hostinfo['invalid_username']])
  554.     stdnse.print_debug(1, "smb-brute: Server's response to invalid passwords: %s", result_short_strings[hostinfo['invalid_password']])
  555.  
  556.     -- If either of these comes back as success, there's no way to tell what's valid/invalid
  557.     if(hostinfo['invalid_username'] == results.SUCCESS) then
  558.         stop_session(hostinfo)
  559.         return false, "Invalid username was accepted; unable to bruteforce"
  560.     end
  561.     if(hostinfo['invalid_password'] == results.SUCCESS) then
  562.         stop_session(hostinfo)
  563.         return false, "Invalid password was accepted; unable to bruteforce"
  564.     end
  565.  
  566.     -- Print a message to the user if we can identify passwords
  567.     if(hostinfo['invalid_username'] ~= hostinfo['invalid_password']) then
  568.         stdnse.print_debug(1, "smb-brute: Invalid username and password response are different, so identifying valid accounts is possible")
  569.     end
  570.  
  571.     -- Print a warning message if invalid_username and invalid_password go to the same thing that isn't FAIL
  572.     if(hostinfo['invalid_username'] ~= results.FAIL and hostinfo['invalid_username'] == hostinfo['invalid_password']) then
  573.         stdnse.print_debug(1, "smb-brute: WARNING: Difficult to recognize invalid usernames/passwords; may not get good results")
  574.     end
  575.  
  576.     -- Restart the SMB connection so we have a clean slate
  577.     stdnse.print_debug(1, "smb-brute: Restarting the session before the bruteforce")
  578.     status, err = restart_session(hostinfo)
  579.     if(status == false) then
  580.         stop_session(hostinfo)
  581.         return false, err
  582.     end
  583.  
  584.     -- Stop the SMB session (we're going to let the scripts look after their own sessions)
  585.     stop_session(hostinfo)
  586.  
  587.     -- Return the results
  588.     return true, hostinfo
  589. end
  590.  
  591. ---Retrieves the next password in the password database we're using. Will never return the empty string.
  592. -- May also return one of the <code>special_passwords</code> constants. 
  593. --
  594. --@param hostinfo The hostinfo table (the password list is stored there). 
  595. --@return The new password, or nil if the end of the list has been reached. 
  596. local function get_next_password(hostinfo)
  597.     local new_password
  598.  
  599.     -- If we're out of special passwords, move onto actual ones
  600.     if(hostinfo['special_password'] > #special_passwords) then
  601.         -- Pick the next non-blank password from the list
  602.         repeat
  603.             new_password = hostinfo['password_list']()
  604.         until new_password ~= ''
  605.     else
  606.         -- Get the next non-blank password
  607.         new_password = special_passwords[hostinfo['special_password']]
  608.         hostinfo['special_password'] = hostinfo['special_password'] + 1
  609.     end
  610.  
  611.     return new_password
  612. end
  613.  
  614. ---Reset to the first password. This is normally done when the user list changes. 
  615. --
  616. --@param hostinfo The hostinfo table. 
  617. local function reset_password(hostinfo)
  618.     hostinfo['password_list']("reset")
  619. end
  620.  
  621. ---Retrieves the next username. This can be from the username database, or from an array stored in the
  622. -- hostinfo table. This won't return any names that have been determined to be invalid, locked, or 
  623. -- have already had their password found. 
  624. --
  625. --@param hostinfo The hostinfo table
  626. --@return The next username, or nil if the end of the list has been reached. 
  627. local function get_next_username(hostinfo)
  628.     local username
  629.  
  630.     repeat
  631.         if(hostinfo['have_user_list']) then
  632.             local index = hostinfo['user_list_index']
  633.             hostinfo['user_list_index'] = hostinfo['user_list_index'] + 1
  634.     
  635.             username = hostinfo['user_list'][index]
  636.             if(username ~= nil) then
  637.                 _, username = split_domain(username)
  638.             end
  639.     
  640.         else
  641.             username = hostinfo['user_list_default']()
  642.         end
  643.  
  644.         -- Make the username lowercase (usernames aren't case sensitive, so making it lower case prevents duplicates)
  645.         if(username ~= nil) then
  646.             username = string.lower(username)
  647.         end
  648.  
  649.     until username == nil or (hostinfo['invalid_usernames'][username] ~= true and hostinfo['locked_usernames'][username] ~= true and hostinfo['accounts'][username] == nil)
  650.  
  651.     return username
  652. end
  653.  
  654. ---Reset to the first username. 
  655. --
  656. --@param hostinfo The hostinfo table. 
  657. local function reset_username(hostinfo)
  658.     if(hostinfo['have_user_list']) then
  659.         hostinfo['user_list_index'] = 1
  660.     else
  661.         hostinfo['user_list_default']("reset")
  662.     end
  663. end
  664.  
  665. ---Do a little trick to detect account lockouts without bringing every user to the lockout threshold -- bump the lockout counter of 
  666. -- the first user ahead. If lockouts are happening, this means that the first account will trigger before the rest of the accounts. 
  667. -- A canary in the mineshaft, in a way.
  668. --
  669. -- The number of checks defaults to three, but it can be controlled with the <code>canary</code> argument. 
  670. --
  671. -- Times it'll fail are when:
  672. -- * Accounts are locked out due to the initial checks (happens if the user runs smb-brute twice in a row, the canary won't help)
  673. -- * A valid user list isn't pulled, and we create a canary that doesn't exist (won't be as bad, though, because it means we also
  674. --   don't have every account on the server/domain
  675. function test_lockouts(hostinfo)
  676.     local i
  677.     local username = get_next_username(hostinfo)
  678.  
  679.     -- It's possible that every username was accounted for already, so our list is empty. 
  680.     if(username == nil) then
  681.         return 
  682.     end
  683.  
  684.     if(nmap.registry.args.smblockout == 1 or nmap.registry.args.smblockout == "true") then
  685.         return
  686.     end
  687.  
  688.     while(string.lower(username) == "administrator") do
  689.         username = get_next_username(hostinfo)
  690.         if(username == nil) then
  691.             return
  692.         end
  693.     end
  694.  
  695.     if(username ~= nil) then
  696.         -- Try logging in as the "canary" account
  697.         local canaries = nmap.registry.args.canaries
  698.         if(canaries == nil) then
  699.             canaries = 3
  700.         else
  701.             canaries = tonumber(canaries)
  702.         end
  703.  
  704.         if(canaries > 0) then
  705.             stdnse.print_debug(1, "smb-brute: Detecting server lockout on '%s' with %d canaries", username, canaries)
  706.         end
  707.  
  708.         for i=1, canaries, 1 do
  709.             result = check_login(hostinfo, username, get_random_string(8), "ntlm")
  710.         end
  711.  
  712.         -- If the account just became locked (it's already been put on the 'valid' list), we're in trouble
  713.         if(result == results.LOCKED) then
  714.             -- If the canary just became locked, we're one step from locking out every account. Loop through the usernames and invalidate them to 
  715.             -- prevent them from being locked out
  716.             stdnse.print_debug(1, "smb-brute: Canary (%s) became locked out -- aborting")
  717.  
  718.             -- Add it to the locked username list (so it can be reported)
  719.             hostinfo['locked_usernames'][username] = true
  720.  
  721.             -- Mark all the usernames as invalid (a bit of a hack, but it's safer this way)
  722.             while(username ~= nil) do
  723.                 stdnse.print_debug(1, "smb-brute: Marking '%s' as 'invalid'", username)
  724.                 hostinfo['invalid_usernames'][username] = true
  725.                 username = get_next_username(hostinfo)
  726.             end
  727.         end
  728.     end
  729.  
  730.     -- Go back to the beginning of the list
  731.     reset_username(hostinfo)
  732. end
  733.  
  734. ---Attempts to validate the current list of usernames by logging in with a blank password, marking invalid ones (and ones that had
  735. -- a blank password). Determining the validity of a username works best if invalid usernames are redirected to 'guest'. 
  736. --
  737. -- If a username accepts the blank password, a random password is tested. If that's accepted as well, the account is marked as 
  738. -- accepting any password (the 'guest' account is normally like that). 
  739. --
  740. -- This also checks whether the server locks out users, and raises the lockout threshold of the first user (see the 
  741. -- <code>check_lockouts</code> function for more information on that. If accounts on the system are locked out, they aren't
  742. -- checked. 
  743. --
  744. --@param hostinfo The hostinfo table. 
  745. --@return (status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. 
  746. local function validate_usernames(hostinfo)
  747.     local status, err
  748.     local result
  749.     local username, password
  750.  
  751.     stdnse.print_debug(1, "smb-brute: Checking which account names exist (based on what goes to the 'guest' account)")
  752.  
  753.     -- Start a session
  754.     status, err = restart_session(hostinfo)
  755.     if(status == false) then
  756.         return false, err
  757.     end
  758.  
  759.     -- Make sure we start at the beginning
  760.     reset_username(hostinfo)
  761.  
  762.     username = get_next_username(hostinfo)
  763.     while(username ~= nil) do
  764.         result = check_login(hostinfo, username, "", "ntlm")
  765.  
  766.         if(result ~= hostinfo['invalid_password'] and result == hostinfo['invalid_username']) then
  767.             -- If the account matches the value of 'invalid_username', but not the value of 'invalid_password', it's invalid
  768.             stdnse.print_debug(1, "smb-brute: Blank password for '%s' -> '%s' (invalid account)", username, result_short_strings[result])
  769.             hostinfo['invalid_usernames'][username] = true
  770.  
  771.         elseif(result == hostinfo['invalid_password']) then
  772.  
  773.             -- If the account matches the value of 'invalid_password', and 'invalid_password' is reliable, it's probably valid
  774.             if(hostinfo['invalid_username'] ~= results.FAIL and hostinfo['invalid_username'] == hostinfo['invalid_password']) then
  775.                 stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (can't determine validity)", username, result_short_strings[result])
  776.             else
  777.                 stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (probably valid)", username, result_short_strings[result])
  778.             end
  779.  
  780.         elseif(result == results.ACCOUNT_LOCKED) then
  781.             -- If the account is locked out, don't try it
  782.             hostinfo['locked_usernames'][username] = true
  783.             stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (locked out)", username, result_short_strings[result])
  784.  
  785.         elseif(result == results.FAIL) then
  786.             -- If none of the standard options work, check if it's FAIL. If it's FAIL, there's an error somewhere (probably, the 
  787.             -- 'administrator' username is changed so we're getting invalid data). 
  788.             stdnse.print_debug(1, "smb-brute: Blank password for '%s' => '%s' (may be valid)", username, result_short_strings[result])
  789.  
  790.         else
  791.             -- If none of those came up, either the password is legitimately blank, or any account works. Figure out what! 
  792.             local new_result = check_login(hostinfo, username, get_random_string(14), "ntlm")
  793.             if(new_result == result) then
  794.                 -- Any password works (often happens with 'guest' account)
  795.                 stdnse.print_debug(1, "smb-brute: All passwords accepted for %s (goes to %s)", username, result_short_strings[result])
  796.                 status, err = found_account(hostinfo, username, "<anything>", result)
  797.                 if(status == false) then
  798.                     return false, err
  799.                 end
  800.             else
  801.                 -- Blank password worked, but not random one
  802.                 status, err = found_account(hostinfo, username, "", result)
  803.                 if(status == false) then
  804.                     return false, err
  805.                 end
  806.             end
  807.         end
  808.  
  809.         username = get_next_username(hostinfo)
  810.     end
  811.  
  812.     -- Start back at the beginning of the list
  813.     reset_username(hostinfo)
  814.  
  815.     -- Check for lockouts
  816.     test_lockouts(hostinfo)
  817.  
  818.     -- Stop the session
  819.     stop_session(hostinfo)
  820.  
  821.     return true
  822. end
  823.  
  824. ---Marks an account as discovered. The login with this account doesn't have to be successful, but <code>is_positive_result</code> should
  825. -- return <code>true</code>. 
  826. --
  827. -- If the result IS successful, and this hasn't been done before, this function will attempt to pull a userlist from the server. 
  828. --
  829. -- The session should be stopped before entering this function, and restarted after -- that allows this function to make its own SMB calls. 
  830. --
  831. --@param hostinfo The hostinfo table. 
  832. --@param username The username. 
  833. --@param password The password.
  834. --@param result   The result, as an integer constant. 
  835. --@return (status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. 
  836. function found_account(hostinfo, username, password, result)
  837.     local status, err
  838.  
  839.     -- Save the username
  840.     hostinfo['accounts'][username] = {}
  841.     hostinfo['accounts'][username]['password'] = password
  842.     hostinfo['accounts'][username]['result']   = result
  843.  
  844.     -- Save the account (smb will automatically decide if it's better than the account it already has)
  845.     if(result == results.SUCCESS) then
  846.         -- Stop the connection -- this lets us do some queries
  847.         status, err = stop_session(hostinfo)
  848.         if(status == false) then
  849.             return false, err
  850.         end
  851.  
  852.         smb.add_account(hostinfo['host'], username, password)
  853.  
  854.         -- If we haven't retrieved the real user list yet, do so
  855.         if(hostinfo['have_user_list'] == false) then
  856.             -- Attempt to enumerate users
  857.             stdnse.print_debug(1, "smb-brute: Trying to get user list from server using newly discovered account")
  858.             hostinfo['have_user_list'], _, hostinfo['user_list'] = msrpc.get_user_list(hostinfo['host'])
  859.             hostinfo['user_list_index'] = 1
  860.             if(hostinfo['have_user_list'] and #hostinfo['user_list'] == 0) then
  861.                 hostinfo['have_user_list'] = false
  862.             end
  863.  
  864.             -- If the list was found, let the user know and reset the password list
  865.             if(hostinfo['have_user_list']) then
  866.                 stdnse.print_debug(1, "smb-brute: Found %d accounts to check!", #hostinfo['user_list'])
  867.                 reset_password(hostinfo)
  868.  
  869.                 -- Validate them (pick out the ones that can't possibly log in)
  870.                 validate_usernames(hostinfo)
  871.             end
  872.         end
  873.  
  874.         -- Start the session again
  875.         status, err = restart_session(hostinfo)
  876.         if(status == false) then
  877.             return false, err
  878.         end
  879.         
  880.     end
  881. end
  882.  
  883. ---This is the main function that does all the work (loops through the lists and checks the results). 
  884. --
  885. --@param host The host table. 
  886. --@return (status, accounts, locked_accounts) If status is false, accounts is an error message. Otherwise, accounts
  887. --        is a table of passwords/results, indexed by the username and locked_accounts is a table indexed by locked
  888. --        usernames. 
  889. local function go(host)
  890.     local status, err
  891.     local result, hostinfo
  892.     local password, temp_password, username
  893.     local response = {}
  894.  
  895.     -- Initialize the hostinfo object, which sets up the initial variables
  896.     result, hostinfo = initialize(host)
  897.     if(result == false) then
  898.         return false, hostinfo
  899.     end
  900.  
  901.     -- If invalid accounts don't give guest, we can determine the existence of users by trying to 
  902.     -- log in with an invalid password and checking the value
  903.     status, err = validate_usernames(hostinfo)
  904.     if(status == false) then
  905.         return false, err
  906.     end
  907.  
  908.     -- Start up the SMB session
  909.     status, err = restart_session(hostinfo)
  910.     if(status == false) then
  911.         return false, err
  912.     end
  913.  
  914.     -- Loop through the password list
  915.     temp_password = get_next_password(hostinfo)
  916.     while(temp_password ~= nil) do
  917.         -- Loop through the user list
  918.         username = get_next_username(hostinfo)
  919.         while(username ~= nil) do
  920.             -- Check if it's a special case (we do this every loop because special cases are often
  921.             -- based on the username
  922.             if(temp_password == USERNAME) then
  923.                 password = username
  924. --io.write(string.format("Trying matching username/password (%s:%s)\n", username, password))
  925.             elseif(temp_password == USERNAME_REVERSED) then
  926.                 password = string.reverse(username)
  927. --io.write(string.format("Trying reversed username/password (%s:%s)\n", username, password))
  928.             else
  929.                 password = temp_password
  930.             end
  931.  
  932. --io.write(string.format("%s:%s\n", username, password))
  933.             local result = check_login(hostinfo, username, password, get_type(hostinfo))
  934.  
  935.             if(is_positive_result(hostinfo, result)) then
  936.  
  937.                 -- First, the special case -- a lockout occurred (bad news!)
  938.                 if(result == results.ACCOUNT_LOCKED) then
  939.                     -- Add it to the list of locked usernames
  940.                     hostinfo['locked_usernames'][username] = true
  941.  
  942.                     -- Unless the user requested to keep going, stop the check
  943.                     if(not(nmap.registry.args.smblockout == 1 or nmap.registry.args.smblockout == "true")) then
  944.                         -- Mark it as found, which is technically true
  945.                         status, err = found_account(hostinfo, username, nil, results.ACCOUNT_LOCKED_NOW)
  946.                         if(status == false) then
  947.                             return err
  948.                         end
  949.  
  950.                         -- Let the user know that it went badly
  951.                         stdnse.print_debug(1, "smb-brute: '%s' became locked out; stopping", username)
  952.  
  953.                         return true, hostinfo['accounts'], hostinfo['locked_usernames']
  954.                     else
  955.                         stdnse.print_debug(1, "smb-brute: '%s' became locked out; continuing", username)
  956.                     end
  957.                 end
  958.  
  959.                 -- Reset the connection
  960.                 stdnse.print_debug(2, "smb-brute: Found an account; resetting connection")
  961.                 status, err = restart_session(hostinfo)
  962.                 if(status == false) then
  963.                     return false, err
  964.                 end
  965.  
  966.                 -- Find the case of the password, unless it's a hash
  967.                 if(not(#password == 32 or #password == 64 or #password == 65)) then
  968.                     stdnse.print_debug(1, "smb-brute: Determining password's case (%s)", format_result(username, password))
  969.                     case_password = find_password_case(hostinfo, username, password, result)
  970.                     stdnse.print_debug(1, "smb-brute: Result: %s", format_result(username, case_password))
  971.                 else
  972.                     case_password = password
  973.                 end
  974.  
  975.                 -- Take normal actions for finding an account
  976.                 status, err = found_account(hostinfo, username, case_password, result)
  977.                 if(status == false) then
  978.                     return err
  979.                 end
  980.             end
  981.             username = get_next_username(hostinfo)
  982.         end
  983.  
  984.         reset_username(hostinfo)
  985.         temp_password = get_next_password(hostinfo)
  986.     end
  987.  
  988.     stop_session(hostinfo)
  989.     return true, hostinfo['accounts'], hostinfo['locked_usernames']
  990. end
  991.  
  992. --_G.TRACEBACK = TRACEBACK or {}
  993. action = function(host, port)
  994. --    TRACEBACK[coroutine.running()] = true;
  995.  
  996.     local status, result
  997.     local response = " \n"
  998.  
  999.     local username
  1000.     local usernames = {}
  1001.     local locked = {}
  1002.     local i
  1003.  
  1004.     status, result, locked_result = go(host)
  1005.     if(status == false) then
  1006.         if(nmap.debugging() > 0) then
  1007.             return "ERROR: " .. result
  1008.         else
  1009.             return nil
  1010.         end
  1011.     end
  1012.  
  1013.     -- Put the usernames in their own table
  1014.     for username in pairs(result) do
  1015.         table.insert(usernames, username)
  1016.     end
  1017.  
  1018.     -- Sort the usernames alphabetically
  1019.     table.sort(usernames)
  1020.  
  1021.     -- Display the usernames
  1022.     if(#usernames == 0) then
  1023.         response = "No accounts found\n"
  1024.     else
  1025.         for i=1, #usernames, 1 do
  1026.             local username = usernames[i]
  1027.             response = response .. format_result(username, result[username]['password'], result[username]['result']) .. "\n"
  1028.         end
  1029.     end
  1030.  
  1031.     -- Make a list of locked accounts
  1032.     for username in pairs(locked_result) do
  1033.         table.insert(locked, username)
  1034.     end
  1035.     if(#locked > 0) then
  1036.         -- Sort the list
  1037.         table.sort(locked)
  1038.  
  1039.         -- Display the list
  1040.         response = response .. string.format("Locked accounts found: %s\n", stdnse.strjoin(", ", locked))
  1041.     end
  1042.  
  1043.     return response
  1044. end
  1045.  
  1046.