-- Make the username lowercase (usernames aren't case sensitive, so making it lower case prevents duplicates)
if(username ~= nil) then
username = string.lower(username)
end
until username == nil or (hostinfo['invalid_usernames'][username] ~= true and hostinfo['locked_usernames'][username] ~= true and hostinfo['accounts'][username] == nil)
return username
end
---Reset to the first username.
--
--@param hostinfo The hostinfo table.
local function reset_username(hostinfo)
if(hostinfo['have_user_list']) then
hostinfo['user_list_index'] = 1
else
hostinfo['user_list_default']("reset")
end
end
---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 <code>canary</code> argument.
--
-- 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
-- don't have every account on the server/domain
function test_lockouts(hostinfo)
local i
local username = get_next_username(hostinfo)
-- It's possible that every username was accounted for already, so our list is empty.
if(username == nil) then
return
end
if(nmap.registry.args.smblockout == 1 or nmap.registry.args.smblockout == "true") then
return
end
while(string.lower(username) == "administrator") do
username = get_next_username(hostinfo)
if(username == nil) then
return
end
end
if(username ~= nil) then
-- Try logging in as the "canary" account
local canaries = nmap.registry.args.canaries
if(canaries == nil) then
canaries = 3
else
canaries = tonumber(canaries)
end
if(canaries > 0) then
stdnse.print_debug(1, "smb-brute: Detecting server lockout on '%s' with %d canaries", username, canaries)
end
for i=1, canaries, 1 do
result = check_login(hostinfo, username, get_random_string(8), "ntlm")
end
-- If the account just became locked (it's already been put on the 'valid' list), we're in trouble
if(result == results.LOCKED) then
-- If the canary just became locked, we're one step from locking out every account. Loop through the usernames and invalidate them to
-- prevent them from being locked out
stdnse.print_debug(1, "smb-brute: Canary (%s) became locked out -- aborting")
-- Add it to the locked username list (so it can be reported)
hostinfo['locked_usernames'][username] = true
-- Mark all the usernames as invalid (a bit of a hack, but it's safer this way)
while(username ~= nil) do
stdnse.print_debug(1, "smb-brute: Marking '%s' as 'invalid'", username)
hostinfo['invalid_usernames'][username] = true
username = get_next_username(hostinfo)
end
end
end
-- Go back to the beginning of the list
reset_username(hostinfo)
end
---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
-- <code>check_lockouts</code> function for more information on that. If accounts on the system are locked out, they aren't
-- checked.
--
--@param hostinfo The hostinfo table.
--@return (status, err) If status is false, err is a string corresponding to the error; otherwise, err is undefined.
local function validate_usernames(hostinfo)
local status, err
local result
local username, password
stdnse.print_debug(1, "smb-brute: Checking which account names exist (based on what goes to the 'guest' account)")
-- Start a session
status, err = restart_session(hostinfo)
if(status == false) then
return false, err
end
-- Make sure we start at the beginning
reset_username(hostinfo)
username = get_next_username(hostinfo)
while(username ~= nil) do
result = check_login(hostinfo, username, "", "ntlm")
if(result ~= hostinfo['invalid_password'] and result == hostinfo['invalid_username']) then
-- If the account matches the value of 'invalid_username', but not the value of 'invalid_password', it's invalid