1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-25 17:09:02 +00:00

Implemented some changes (mostly in error handling) to let smb scripts run in parallel with each other against a single host. Previously, I had used a mutex to prevent that from happening. The changes are summarized in the following post: http://seclists.org/nmap-dev/2010/q3/819, but it comes down to three things:

1) Set the 'vc' ('virtual circuit') id to a non-zero value (if it's 0, the smb server will disconnect all hosts who are already connected)
2) Handle the authentication error NT_STATUS_REQUEST_NOT_ACCEPTED, which indicates that there are too many connected hosts (11 is the default on most versions of Windows, or 10 for the anonymous account). If we see the error, we wait and try again. 
3) Handle the file creation error, NT_STATUS_PIPE_NOT_AVAILABLE, which appears to be caused by a race condition of some sort. It happens when a large number of connections are attempted simultaneously, and is fixed by a short backoff (50ms worked fine, but I'm using 100ms)
The end result is a significant speedup in our SMB checks without losing data.
This commit is contained in:
ron
2010-10-03 22:40:41 +00:00
parent 380cfaba2c
commit 727661921a

View File

@@ -132,9 +132,6 @@ command_names = {}
status_codes = {}
status_names = {}
local mutexes = setmetatable({}, {__mode = "k"});
--local debug_mutex = nmap.mutex("SMB-DEBUG")
local TIMEOUT = 10000
---Wrapper around <code>smbauth.add_account</code>.
@@ -174,68 +171,6 @@ function get_overrides_anonymous(overrides)
end
end
---Returns the mutex that should be used by the current connection. This mutex attempts
-- to use the name, first, then falls back to the IP if no name was returned.
--
--@param smbstate The SMB object associated with the connection
--@return A mutex
local function get_mutex(smbstate)
local mutex_name = "SMB-"
local mutex
-- if(nmap.debugging() > 0) then
-- return debug_mutex
-- end
-- Decide whether to use the name or the ip address as the unique identifier
if(smbstate['name'] ~= nil) then
mutex_name = mutex_name .. smbstate['name']
else
mutex_name = mutex_name .. smbstate['ip']
end
if(mutexes[smbstate] == nil) then
mutex = nmap.mutex(mutex_name)
mutexes[smbstate] = mutex
else
mutex = mutexes[smbstate]
end
stdnse.print_debug(3, "SMB: Using mutex named '%s'", mutex_name)
return mutex
end
---Locks the mutex being used by this host. Doesn't return until it successfully
-- obtains a lock.
--
--@param smbstate The SMB object associated with the connection
--@param func A name to associate with this call (used purely for debugging
-- and logging)
local function lock_mutex(smbstate, func)
local mutex
stdnse.print_debug(3, "SMB: Attempting to lock mutex [%s]", func)
mutex = get_mutex(smbstate)
mutex "lock"
stdnse.print_debug(3, "SMB: Mutex lock obtained [%s]", func)
end
---Unlocks the mutex being used by this host.
--
--@param smbstate The SMB object associated with the connection
--@param func A name to associate with this call (used purely for debugging
-- and logging)
local function unlock_mutex(smbstate, func)
local mutex
stdnse.print_debug(3, "SMB: Attempting to release mutex [%s]", func)
mutex = get_mutex(smbstate)
mutex "done"
stdnse.print_debug(3, "SMB: Mutex released [%s]", func)
end
---Convert a status number from the SMB header into a status name, returning an error message (not nil) if
-- it wasn't found.
--
@@ -304,9 +239,7 @@ function disable_extended(smb)
smb['extended_security'] = false
end
--- Begins a SMB session, automatically determining the best way to connect. Also starts a mutex
-- with mutex_id. This prevents multiple threads from making queries at the same time (which breaks
-- SMB).
--- Begins a SMB session, automatically determining the best way to connect.
--
-- @param host The host object
-- @return (status, smb) if the status is true, result is the newly crated smb object;
@@ -318,6 +251,8 @@ function start(host)
state['uid'] = 0
state['tid'] = 0
state['mid'] = 1
state['pid'] = math.random(32766) + 1
state['host'] = host
state['ip'] = host.ip
state['sequence'] = -1
@@ -341,17 +276,14 @@ function start(host)
return false, "SMB: Couldn't find a valid port to check"
end
-- Initialize the accounts for logging on (note: this has to be outside the mutex, or things break)
-- Initialize the accounts for logging on
smbauth.init_account(host)
lock_mutex(state, "start(1)")
if(port ~= 139) then
status, state['socket'] = start_raw(host, port)
state['port'] = port
if(status == false) then
unlock_mutex(state, "start(1)")
return false, state['socket']
end
return true, state
@@ -360,15 +292,12 @@ function start(host)
status, state['socket'] = start_netbios(host, port)
state['port'] = port
if(status == false) then
unlock_mutex(state, "start(2)")
return false, state['socket']
end
return true, state
end
unlock_mutex(state, "start(3)")
return false, "SMB: Couldn't find a valid port to check"
end
@@ -447,8 +376,7 @@ function start_ex(host, negotiate_protocol, start_session, tree_connect, create_
return true, smbstate
end
--- Kills the SMB connection, closes the socket, and releases the mutex. Because of the mutex
-- being released, a script HAS to call <code>stop</code> before it exits, no matter why it's exiting!
--- Kills the SMB connection and closes the socket.
--
-- In addition to killing the connection, this function will log off the user and disconnect
-- the connected tree, if possible.
@@ -466,8 +394,6 @@ function stop(smb)
logoff(smb)
end
unlock_mutex(smb, "stop()")
stdnse.print_debug(2, "SMB: Closing socket")
if(smb['socket'] ~= nil) then
local status, err = smb['socket']:close()
@@ -721,9 +647,9 @@ local function smb_encode_header(smb, command, overrides)
(overrides['signature'] or 0), -- extra (signature)
(overrides['extra'] or 0), -- extra (unused)
(overrides['tid'] or smb['tid']), -- tid
(overrides['pid'] or 12345), -- pid
(overrides['pid'] or smb['pid']), -- pid
(overrides['uid'] or smb['uid']), -- uid
(overrides['uid'] or 0) -- mid
(overrides['mid'] or smb['mid']) -- mid
)
return header
@@ -1152,7 +1078,7 @@ function negotiate_protocol(smb, overrides)
end
function start_session_basic(smb, log_errors, overrides)
local function start_session_basic(smb, log_errors, overrides)
local i, err
local status, result
local header, parameters, data, domain
@@ -1161,6 +1087,7 @@ function start_session_basic(smb, log_errors, overrides)
local andx_command, andx_reserved, andx_offset, action
local os, lanmanager
local username, domain, password, password_hash, hash_type
local busy_count = 0
header = smb_encode_header(smb, command_codes['SMB_COM_SESSION_SETUP_ANDX'], overrides)
@@ -1188,7 +1115,7 @@ function start_session_basic(smb, log_errors, overrides)
0x0000, -- ANDX -- next offset
0xFFFF, -- Max buffer size
0x0001, -- Max multiplexes
0x0000, -- Virtual circuit num
0x0001, -- Virtual circuit num
smb['session_key'], -- The session key
#lanman, -- ANSI/Lanman password length
#ntlm, -- Unicode/NTLM password length
@@ -1269,17 +1196,30 @@ function start_session_basic(smb, log_errors, overrides)
return true
else
-- This username failed, print a warning and keep going
if(log_errors == nil or log_errors == true) then
stdnse.print_debug(1, "SMB: Login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), get_status_name(status))
end
-- Check if we got the error NT_STATUS_REQUEST_NOT_ACCEPTED
if(status == 0xc00000d0) then
busy_count = busy_count + 1
-- Go to the next account
if(overrides == nil or overrides['username'] == nil) then
smbauth.next_account(smb['host'])
result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
if(busy_count > 9) then
return false, "SMB: ERROR: Server has too many active connections; giving up."
end
local backoff = math.random() * 10
stdnse.print_debug(1, "SMB: Server has too many active connections; pausing for %s seconds.", math.floor(backoff * 100) / 100)
stdnse.sleep(backoff)
else
result = false
-- This username failed, print a warning and keep going
if(log_errors == nil or log_errors == true) then
stdnse.print_debug(1, "SMB: Login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), get_status_name(status))
end
-- Go to the next account
if(overrides == nil or overrides['username'] == nil) then
smbauth.next_account(smb['host'])
result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
else
result = false
end
end
end
end
@@ -1291,7 +1231,7 @@ function start_session_basic(smb, log_errors, overrides)
return false, username
end
function start_session_extended(smb, log_errors, overrides)
local function start_session_extended(smb, log_errors, overrides)
local i
local status, status_name, result, err
local header, parameters, data
@@ -1300,6 +1240,7 @@ function start_session_extended(smb, log_errors, overrides)
local andx_command, andx_reserved, andx_offset, action, security_blob_length
local os, lanmanager
local username, domain, password, password_hash, hash_type
local busy_count = 0
-- Set a default status_name, in case everything fails
status_name = "An unknown error has occurred"
@@ -1342,7 +1283,7 @@ function start_session_extended(smb, log_errors, overrides)
0x0000, -- ANDX -- next offset
0xFFFF, -- Max buffer size
0x0001, -- Max multiplexes
0x0000, -- Virtual circuit num
0x0001, -- Virtual circuit num
smb['session_key'], -- The session key
#security_blob, -- Security blob length
0x00000000, -- Reserved
@@ -1423,24 +1364,38 @@ function start_session_extended(smb, log_errors, overrides)
end -- Should we parse the parameters/data?
until status_name ~= "NT_STATUS_MORE_PROCESSING_REQUIRED"
-- Display a message to the user, and try the next account
if(log_errors == nil or log_errors == true) then
stdnse.print_debug(1, "SMB: Extended login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), status_name)
-- Check if we got the error NT_STATUS_REQUEST_NOT_ACCEPTED
if(status == 0xc00000d0) then
busy_count = busy_count + 1
if(busy_count > 9) then
return false, "SMB: ERROR: Server has too many active connections; giving up."
end
local backoff = math.random() * 10
stdnse.print_debug(1, "SMB: Server has too many active connections; pausing for %s seconds.", math.floor(backoff * 100) / 100)
stdnse.sleep(backoff)
else
-- Display a message to the user, and try the next account
if(log_errors == nil or log_errors == true) then
stdnse.print_debug(1, "SMB: Extended login as %s\\%s failed (%s)", domain, stdnse.string_or_blank(username), status_name)
end
-- Go to the next account
if(overrides == nil or overrides['username'] == nil) then
smbauth.next_account(smb['host'])
result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
if(not(result)) then
return false, username
end
else
result = false
end
end
-- Reset the user id and security_blob
-- Reset the user id
smb['uid'] = 0
-- Go to the next account
if(overrides == nil or overrides['username'] == nil) then
smbauth.next_account(smb['host'])
result, username, domain, password, password_hash, hash_type = smbauth.get_account(smb['host'])
if(not(result)) then
return false, username
end
else
result = false
end
end -- Loop over the accounts
if(log_errors == nil or log_errors == true) then
@@ -1666,50 +1621,67 @@ function create_file(smb, path, overrides)
local header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, pid, mid
local andx_command, andx_reserved, andx_offset
local oplock_level, fid, create_action, created, last_access, last_write, last_change, attributes, allocation_size, end_of_file, filetype, ipc_state, is_directory
local error_count = 0
-- Make sure we have overrides
overrides = overrides or {}
repeat
local mutex = nmap.mutex(smb['host'])
mutex "lock"
-- Make sure we have overrides
overrides = overrides or {}
header = smb_encode_header(smb, command_codes['SMB_COM_NT_CREATE_ANDX'], overrides)
parameters = bin.pack("<CCSCSIIILIIIIIC",
0xFF, -- ANDX no further commands
0x00, -- ANDX reserved
0x0000, -- ANDX offset
0x00, -- Reserved
string.len(path), -- Path length
(overrides['file_create_flags'] or 0x00000016), -- Create flags
(overrides['file_create_root_fid'] or 0x00000000), -- Root FID
(overrides['file_create_access_mask'] or 0x02000000), -- Access mask
(overrides['file_create_allocation_size'] or 0x0000000000000000), -- Allocation size
(overrides['file_create_attributes'] or 0x00000000), -- File attributes
(overrides['file_create_share_attributes'] or 0x00000007), -- Share attributes
(overrides['file_create_disposition'] or 0x00000000), -- Disposition
(overrides['file_create_options'] or 0x00000000), -- Create options
(overrides['file_create_impersonation'] or 0x00000002), -- Impersonation
(overrides['file_create_security_flags'] or 0x01) -- Security flags
)
data = bin.pack("z", path)
-- Send the create file
stdnse.print_debug(2, "SMB: Sending SMB_COM_NT_CREATE_ANDX")
local result, err = smb_send(smb, header, parameters, data, overrides)
if(result == false) then
mutex "done"
return false, err
end
-- Read the result
status, header, parameters, data = smb_read(smb, false)
mutex "done"
if(status ~= true) then
return false, header
end
header = smb_encode_header(smb, command_codes['SMB_COM_NT_CREATE_ANDX'], overrides)
parameters = bin.pack("<CCSCSIIILIIIIIC",
0xFF, -- ANDX no further commands
0x00, -- ANDX reserved
0x0000, -- ANDX offset
0x00, -- Reserved
string.len(path), -- Path length
(overrides['file_create_flags'] or 0x00000016), -- Create flags
(overrides['file_create_root_fid'] or 0x00000000), -- Root FID
(overrides['file_create_access_mask'] or 0x02000000), -- Access mask
(overrides['file_create_allocation_size'] or 0x0000000000000000), -- Allocation size
(overrides['file_create_attributes'] or 0x00000000), -- File attributes
(overrides['file_create_share_attributes'] or 0x00000007), -- Share attributes
(overrides['file_create_disposition'] or 0x00000000), -- Disposition
(overrides['file_create_options'] or 0x00000000), -- Create options
(overrides['file_create_impersonation'] or 0x00000002), -- Impersonation
(overrides['file_create_security_flags'] or 0x01) -- Security flags
)
-- Check if we were allowed in
local uid, tid
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(header1 == nil or mid == nil) then
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [23]"
end
if(status == 0xc00000ac) then
error_count = error_count + 1
if(error_count > 10) then
return false, "SMB: ERROR: Server returned NT_STATUS_PIPE_NOT_AVAILABLE too many times; giving up."
end
stdnse.print_debug(1, "WARNING: Server refused connection with NT_STATUS_PIPE_NOT_AVAILABLE; trying again")
stdnse.sleep(.2)
end
until (status ~= 0xc00000ac)
data = bin.pack("z", path)
-- Send the create file
stdnse.print_debug(2, "SMB: Sending SMB_COM_NT_CREATE_ANDX")
local result, err = smb_send(smb, header, parameters, data, overrides)
if(result == false) then
return false, err
end
-- Read the result
status, header, parameters, data = smb_read(smb, false)
if(status ~= true) then
return false, header
end
-- Check if we were allowed in
local uid, tid
pos, header1, header2, header3, header4, command, status, flags, flags2, pid_high, signature, unused, tid, pid, uid, mid = bin.unpack("<CCCCCICSSlSSSSS", header)
if(header1 == nil or mid == nil) then
return false, "SMB: ERROR: Server returned less data than it was supposed to (one or more fields are missing); aborting [23]"
end
if(status ~= 0) then
return false, get_status_name(status)
end