File smb-brute
Download: http://nmap.org/svn/scripts/smb-brute.nse
User Summary
Attempts to guess username/password combinations over SMB, storing discovered combinations for use in other scripts. Every attempt will be made to get a valid list of users and to verify each username before actually using them. When a username is discovered, besides being printed, it is also saved in the Nmap registry so other Nmap scripts can use it. That means that if you're going to run smb-brute.nse, you should run other smb scripts you want. This checks passwords in a case-insensitive way, determining case after a password is found, for Windows versions before Vista.
This script is specifically targeted towards security auditors or penetration testers. One example of its use, suggested by Brandon Enright, was hooking up smb-brute.nse to the database of usernames and passwords used by the Conficker worm (the password list can be found here, among other places <http://www.skullsecurity.org/wiki/index.php/Passwords>. Then, the network is scanned and all systems that would be infected by Conficker are discovered.
From the penetration tester perspective its use is pretty obvious. By discovering weak passwords on SMB, a protocol that's well suited for bruteforcing, access to a system can be gained. Further, passwords discovered against Windows with SMB might also be used on Linux or MySQL or custom Web applications. Discovering a password greatly beneficial for a pen-tester.
This script uses a lot of little tricks that I (Ron Bowes) describe in detail in a blog posting <http://www.skullsecurity.org/blog/?p=164>. The tricks will be summarized here, but that blog is the best place to learn more.
Usernames and passwords are initially taken from the unpw library. If possible, the usernames are verified as existing by taking advantage of Windows' odd behaviour with invalid username and invalid password responses. As soon as it is able, this script will download a full list of usernames from the server and replace the unpw usernames with those. This enables the script to restrict itself to actual accounts only.
When an account is discovered, it's saved in the smb module (which uses the Nmap
registry). If an account is already saved, the account's privileges are checked; accounts
with administrator privileges are kept over accounts without. The specific method for checking
is by calling GetShareInfo("IPC$"), which requires administrative privileges. Once this script
is finished (all other smb scripts depend on it, it'll run first), other scripts will use the saved account
to perform their checks.
The blank password is always tried first, followed by "special passwords" (such as the username and the username reversed). Once those are exhausted, the unpw password list is used.
One major goal of this script is to avoid accout lockouts. This is done in a few ways. First,
when a lockout is detected, unless you user specifically overrides it with the smblockout
argument, the scan stops. Second, all usernames are checked with the most common passwords first,
so with not-too-strict lockouts (10 invalid attempts), the 10 most common passwords will still
be tried. Third, one account, called the canary, 'goes out ahead' -- that is, three invalid
attempts are made (by default) to ensure that it's locked out before others are.
In addition to active accounts, this script will identify valid passwords for accounts that are disabled, guest-equivalent, and require password changes. Although these accounts can't be used, it's good to know that the password is valid. In other cases, it's impossible to tell a valid password (if an account is locked out, for example). These are displayed, too. Certain accounts, such as guest or some guest-equivalent, will permit any password. This is also detected. When possible, the SMB protocol is used to its fullest to get maximum information.
When possible, checks are done using a case-insensitive password, then proper case is determined with a fairly efficient bruteforce. For example, if the actual password is 'PassWord', then 'password' will work and 'PassWord' will be found afterwards (on the 14th attempt out of a possible 256 attempts, with the current algorithm).
Script Arguments
smblockout
Unless this is set to '1' or 'true', the script won't continue if it locks out an account or thinks it will lock out an account.
canaries
Sets the number of tests to do to attempt to lock out the first account. This will lock out the first account without locking out the rest of the accounts. The default is 3, which will only trigger strict lockouts, but will also bump the canary account up far enough to detect a lockout well before other accounts are hit.
brutelimit
Limits the number of usernames checked in the script. In some domains, it's possible to end up with 10,000+ usernames on each server. By default, this will be 5000, which should be higher than most servers and also prevent infinite loops or other weird things. This will only affect the user list pulled from the server, not the username list.
passdb, userdb
See the documentation for the unpwdb library.randomseed, smbbasic, smbport, smbsign
See the documentation for the smb library.smbdomain, smbhash, smbpassword, smbtype, smbusername
See the documentation for the smbauth library.Example Usage
nmap --script smb-brute.nse -p445 <host> sudo nmap -sU -sS --script smb-brute.nse -p U:137,T:139 <host>
Script Output
Host script results: | smb-brute: | | bad name:test => Login was successful | | consoletest:test => Password was correct, but user can't log in without changing it | | guest:<anything> => Password was correct, but user's account is disabled | | mixcase:BuTTeRfLY1 => Login was successful | | test:password1 => Login was successful | | this:password => Login was successful | | thisisaverylong:password => Login was successful | | thisisaverylongname:password => Login was successful | | thisisaverylongnamev:password => Login was successful |_ |_ web:TeSt => Password was correct, but user's account is disabled
Requires
author Ron Bowes
copyright © Same as Nmap--See http://nmap.org/book/man-legal.html
Functions
| bad_lockout_policy (host) | Unless the user is ok with lockouts, check the lockout policy of the host. Take the most restrictive portion among the domains. Returns true if lockouts could happen, false otherwise. |
| check_login (hostinfo, username, password, logintype) |
Attempts to log into an account, returning one of the |
| convert_case (str, num) | Converts a string's case based on a binary number. For every '1' bit, the character is uppercased, and for every '0' bit it's lowercased. For example, "test" and 8 (1000) becomes "Test", while "test" and 11 (1011) becomes "TeST". |
| count_ones (num) | Count the number of one bits in a binary representation of the given number. This is used for case-sensitive checks. |
| find_password_case (hostinfo, username, password) | Attempts to determine the case of a password. This is done by trying every possible combination of upper and lowercase characters in the password, in the most efficient possible ordering, until the corerct case is found. |
| format_result (username, password, result) | Formats a username/password pair with an optional result. Just a way to keep things consistent throughout the program. Currently, the format is "username:password => result". |
| found_account (hostinfo, username, password, result) |
Marks an account as discovered. The login with this account doesn't have to be successful, but |
| get_next_password (hostinfo) |
Retrieves the next password in the password database we're using. Will never return the empty string.
May also return one of the |
| get_next_username (hostinfo) | Retrieves the next username. This can be from the username database, or from an array stored in the hostinfo table. This won't return any names that have been determined to be invalid, locked, or have already had their password found. |
| get_random_string (length, set) | Generates a random string of the requested length. This can be used to check how hosts react to weird username/password combinations. |
| get_type (hostinfo) | Decides which login type to use (lanman, ntlm, or other). Designed to keep things consistent. |
| go (host) | This is the main function that does all the work (loops through the lists and checks the results). |
| initialize (host) | Initializes and returns the hostinfo table. This includes queuing up the username and password lists, determining the server's operating system, and checking the server's response for invalid usernames/invalid passwords. |
| is_bad_result (hostinfo, result) | Determines whether or not a login was "bad". A bad login is one where an account becomes locked out. |
| is_positive_result (hostinfo, result) | Determines whether or not a login was successful, based on what's known about the server's settings. This is fairly straight forward, but has a couple little tricks. |
| reset_password (hostinfo) | Reset to the first password. This is normally done when the user list changes. |
| reset_username (hostinfo) | Reset to the first username. |
| restart_session (hostinfo) |
Starts or restarts a SMB session with the host. Although this will automatically stop a session if
one exists, it's a little cleaner to pair this with a |
| split_domain (str) | Splits a string in the form "domain\user" into domain and user. |
| stop_session (hostinfo) |
Stops the session, if one exists. This can be called as frequently as needed, it'll just return if no
session is present, but it should generally be paired with a |
| test_lockouts (hostinfo) | Do a little trick to detect account lockouts without bringing every user to the lockout threshold -- bump the lockout counter of the first user ahead. If lockouts are happening, this means that the first account will trigger before the rest of the accounts. A canary in the mineshaft, in a way. |
| validate_usernames (hostinfo) | Attempts to validate the current list of usernames by logging in with a blank password, marking invalid ones (and ones that had a blank password). Determining the validity of a username works best if invalid usernames are redirected to 'guest'. |
Functions
- bad_lockout_policy (host)
-
Unless the user is ok with lockouts, check the lockout policy of the host. Take the most restrictive portion among the domains. Returns true if lockouts could happen, false otherwise.
Parameters
- host:
- check_login (hostinfo, username, password, logintype)
-
Attempts to log into an account, returning one of the
resultsconstants. Will always return to the state where another login can be attempted. Will also differentiate between a hash and a password, and choose the proper login method (unless overridden). Will interpret the result as much as possible.The session has to be active (ie,
restart_sessionhas to be called) before calling this function.Parameters
- hostinfo: The hostinfo table.
- username: The username to try.
- password: The password to try.
-
logintype:
[optional] The logintype to use. Default:
get_typeis called. Ifpasswordis a hash, this is ignored.
Return value:
Result, an integer value from theresultsconstants. - convert_case (str, num)
-
Converts a string's case based on a binary number. For every '1' bit, the character is uppercased, and for every '0' bit it's lowercased. For example, "test" and 8 (1000) becomes "Test", while "test" and 11 (1011) becomes "TeST".
Parameters
- str: The string to convert.
- 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 too small it's effectively zero-padded.
Return value:
The converted string. - count_ones (num)
-
Count the number of one bits in a binary representation of the given number. This is used for case-sensitive checks.
Parameters
- num: The number to count the ones for.
Return value:
The number of ones in the number - find_password_case (hostinfo, username, password)
-
Attempts to determine the case of a password. This is done by trying every possible combination of upper and lowercase characters in the password, in the most efficient possible ordering, until the corerct case is found.
A session has to be active when this function is called.
Parameters
- hostinfo: The hostinfo table.
- username: The username.
- password: The password (it's assumed that it's all lowercase already, but it doesn't matter)
Return value:
The password with the proper case, or the original password if it couldn't be determined (either the proper case wasn't found or the login type is incorrect). - format_result (username, password, result)
-
Formats a username/password pair with an optional result. Just a way to keep things consistent throughout the program. Currently, the format is "username:password => result".
Parameters
- username: The username.
- password: [optional] The password. Default: "<unknown>".
- result: [optional] The result, as a constant. Default: not used.
Return value:
A string representing the input values. - found_account (hostinfo, username, password, result)
-
Marks an account as discovered. The login with this account doesn't have to be successful, but
is_positive_resultshould returntrue.If the result IS successful, and this hasn't been done before, this function will attempt to pull a userlist from the server.
The session should be stopped before entering this function, and restarted after -- that allows this function to make its own SMB calls.
Parameters
- hostinfo: The hostinfo table.
- username: The username.
- password: The password.
- result: The result, as an integer constant.
Return value:
(status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. - get_next_password (hostinfo)
-
Retrieves the next password in the password database we're using. Will never return the empty string. May also return one of the
special_passwordsconstants.Parameters
- hostinfo: The hostinfo table (the password list is stored there).
Return value:
The new password, or nil if the end of the list has been reached. - get_next_username (hostinfo)
-
Retrieves the next username. This can be from the username database, or from an array stored in the hostinfo table. This won't return any names that have been determined to be invalid, locked, or have already had their password found.
Parameters
- hostinfo: The hostinfo table
Return value:
The next username, or nil if the end of the list has been reached. - get_random_string (length, set)
-
Generates a random string of the requested length. This can be used to check how hosts react to weird username/password combinations.
Parameters
- length: (optional) The length of the string to return. Default: 8.
- set: (optional) The set of letters to choose from. Default: upper, lower, numbers, and underscore.
Return value:
The random string. - get_type (hostinfo)
-
Decides which login type to use (lanman, ntlm, or other). Designed to keep things consistent.
Parameters
- hostinfo: The hostinfo table.
Return value:
A string representing the login type to use (that can be passed to SMB functions). - go (host)
-
This is the main function that does all the work (loops through the lists and checks the results).
Parameters
- host: The host table.
Return value:
(status, accounts, locked_accounts) If status is false, accounts is an error message. Otherwise, accounts is a table of passwords/results, indexed by the username and locked_accounts is a table indexed by locked usernames. - initialize (host)
-
Initializes and returns the hostinfo table. This includes queuing up the username and password lists, determining the server's operating system, and checking the server's response for invalid usernames/invalid passwords.
Parameters
- host: The host object.
- is_bad_result (hostinfo, result)
-
Determines whether or not a login was "bad". A bad login is one where an account becomes locked out.
Parameters
- hostinfo: The hostinfo table.
- result: The result code.
Return value:
trueif the password used for logging in was correct,falseotherwise. Keep in mind that this doesn't imply the login was successful (only results.SUCCESS indicates that), rather that the password was valid. - is_positive_result (hostinfo, result)
-
Determines whether or not a login was successful, based on what's known about the server's settings. This is fairly straight forward, but has a couple little tricks.
Parameters
- hostinfo: The hostinfo table.
- result: The result code.
Return value:
trueif the password used for logging in was correct,falseotherwise. Keep in mind that this doesn't imply the login was successful (only results.SUCCESS indicates that), rather that the password was valid. - reset_password (hostinfo)
-
Reset to the first password. This is normally done when the user list changes.
Parameters
- hostinfo: The hostinfo table.
- reset_username (hostinfo)
-
Reset to the first username.
Parameters
- hostinfo: The hostinfo table.
- restart_session (hostinfo)
-
Starts or restarts a SMB session with the host. Although this will automatically stop a session if one exists, it's a little cleaner to pair this with a
stop_sessioncall.Parameters
- hostinfo: The hostinfo table.
Return value:
(status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. - split_domain (str)
-
Splits a string in the form "domain\user" into domain and user.
Parameters
- str: The string to split
Return value:
(domain, username) The domain and the username. If no domain was given, nil is returned for domain. - stop_session (hostinfo)
-
Stops the session, if one exists. This can be called as frequently as needed, it'll just return if no session is present, but it should generally be paired with a
restart_sessioncall.Parameters
- hostinfo: The hostinfo table.
Return value:
(status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined. - test_lockouts (hostinfo)
-
Do a little trick to detect account lockouts without bringing every user to the lockout threshold -- bump the lockout counter of the first user ahead. If lockouts are happening, this means that the first account will trigger before the rest of the accounts. A canary in the mineshaft, in a way.
The number of checks defaults to three, but it can be controlled with the
canaryargument.Times it'll fail are when:
- 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)
- 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
Parameters
- hostinfo:
- validate_usernames (hostinfo)
-
Attempts to validate the current list of usernames by logging in with a blank password, marking invalid ones (and ones that had a blank password). Determining the validity of a username works best if invalid usernames are redirected to 'guest'.
If a username accepts the blank password, a random password is tested. If that's accepted as well, the account is marked as accepting any password (the 'guest' account is normally like that).
This also checks whether the server locks out users, and raises the lockout threshold of the first user (see the
check_lockoutsfunction for more information on that. If accounts on the system are locked out, they aren't checked.Parameters
- hostinfo: The hostinfo table.
Return value:
(status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined.




