1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-10 08:41:34 +00:00

Merge from /nmap-exp/ron/nmap-smb. This adds the new scripts

smb-serverstats.nse, smb-enumsessions.nse, and smb-enumshares.nse.
This commit is contained in:
david
2008-11-03 20:00:24 +00:00
parent 2cceb5184c
commit cc7a58cd7a
11 changed files with 4644 additions and 1779 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
-- NetBIOS name requests. -- NetBIOS name requests.
-- --
--@author Ron Bowes <ron@skullsecurity.net> --@author Ron Bowes <ron@skullsecurity.net>
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html --@copyright See nmaps COPYING for licence
----------------------------------------------------------------------- -----------------------------------------------------------------------
module(... or "netbios", package.seeall) module(... or "netbios", package.seeall)
@@ -16,12 +16,12 @@ require 'stdnse'
-- character, and converted it to all uppercase characters (so it can, for example, -- character, and converted it to all uppercase characters (so it can, for example,
-- pass case-sensitive data in a case-insensitive way) -- pass case-sensitive data in a case-insensitive way)
-- --
-- There are two levels of encoding performed:\n -- There are two levels of encoding performed:
-- L1: Pad the string to 16 characters withs spaces (or NULLs if it's the -- * L1: Pad the string to 16 characters withs spaces (or NULLs if it's the
-- wildcard "*") and replace each byte with two bytes representing each -- wildcard "*") and replace each byte with two bytes representing each
-- of its nibbles, plus 0x41. \n -- of its nibbles, plus 0x41.
-- L2: Prepend the length to the string, and to each substring in the scope -- * L2: Prepend the length to the string, and to each substring in the scope
-- (separated by periods). \n -- (separated by periods).
--@param name The name that will be encoded (eg. "TEST1"). --@param name The name that will be encoded (eg. "TEST1").
--@param scope [optional] The scope to encode it with. I've never seen scopes used --@param scope [optional] The scope to encode it with. I've never seen scopes used
-- in the real world (eg, "insecure.org"). -- in the real world (eg, "insecure.org").
@@ -76,7 +76,7 @@ end
-- the string representation. If the encoding is invalid, it will still attempt -- the string representation. If the encoding is invalid, it will still attempt
-- to decode the string as best as possible. -- to decode the string as best as possible.
--@param encoded_name The L2-encoded name --@param encoded_name The L2-encoded name
--@return the decoded name and the scope. The name will still be padded, and the --@returns the decoded name and the scope. The name will still be padded, and the
-- scope will never be nil (empty string is returned if no scope is present) -- scope will never be nil (empty string is returned if no scope is present)
function name_decode(encoded_name) function name_decode(encoded_name)
local name = "" local name = ""
@@ -208,38 +208,41 @@ end
-- script has already performed a nbstat query, the result can be re-used. -- script has already performed a nbstat query, the result can be re-used.
-- --
-- The NetBIOS request's header looks like this: -- The NetBIOS request's header looks like this:
-- --------------------------------------------------\n --<code>
-- | 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |\n -- --------------------------------------------------
-- | NAME_TRN_ID |\n -- | 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 |
-- | R | OPCODE | NM_FLAGS | RCODE | (FLAGS)\n -- | NAME_TRN_ID |
-- | QDCOUNT |\n -- | R | OPCODE | NM_FLAGS | RCODE | (FLAGS)
-- | ANCOUNT |\n -- | QDCOUNT |
-- | NSCOUNT |\n -- | ANCOUNT |
-- | ARCOUNT |\n -- | NSCOUNT |
-- --------------------------------------------------\n -- | ARCOUNT |
-- --------------------------------------------------
--</code>
-- --
-- In this case, the TRN_ID is a constant (0x1337, what else?), the flags -- In this case, the TRN_ID is a constant (0x1337, what else?), the flags
-- are 0, and we have one question. All fields are network byte order. -- are 0, and we have one question. All fields are network byte order.
-- --
-- The body of the packet is a list of names to check for in the following -- The body of the packet is a list of names to check for in the following
-- format: -- format:
-- (ntstring) encoded name -- * (ntstring) encoded name
-- (2 bytes) query type (0x0021 = NBSTAT) -- * (2 bytes) query type (0x0021 = NBSTAT)
-- (2 bytes) query class (0x0001 = IN) -- * (2 bytes) query class (0x0001 = IN)
-- --
-- The response header is the exact same, except it'll have some flags set -- The response header is the exact same, except it'll have some flags set
-- (0x8000 for sure, since it's a response), and ANCOUNT will be 1. The format -- (0x8000 for sure, since it's a response), and ANCOUNT will be 1. The format
-- of the answer is:\n -- of the answer is:
-- (ntstring) requested name\n --
-- (2 bytes) query type\n -- * (ntstring) requested name
-- (2 bytes) query class\n -- * (2 bytes) query type
-- (2 bytes) time to live\n -- * (2 bytes) query class
-- (2 bytes) record length\n -- * (2 bytes) time to live
-- (1 byte) number of names\n -- * (2 bytes) record length
-- [for each name]\n -- * (1 byte) number of names
-- (16 bytes) padded name, with a 1-byte suffix\n -- * [for each name]
-- (2 bytes) flags\n -- * (16 bytes) padded name, with a 1-byte suffix
-- (variable) statistics (usually mac addres) -- * (2 bytes) flags
-- * (variable) statistics (usually mac address)
-- --
--@param host The IP or hostname of the system. --@param host The IP or hostname of the system.
--@return (status, names, statistics) If status is true, then the servers names are --@return (status, names, statistics) If status is true, then the servers names are
@@ -255,7 +258,7 @@ function do_nbstat(host)
stdnse.print_debug(1, "Performing nbstat on host '%s'", host) stdnse.print_debug(1, "Performing nbstat on host '%s'", host)
-- Check if it's cased in the registry for this host -- Check if it's cased in the registry for this host
if(nmap.registry["nbstat_names_" .. host] ~= nil) then if(nmap.registry["nbstat_names_" .. host] ~= nil) then
stdnse.print_debug(1, " [using cached value]") stdnse.print_debug(1, " |_ [using cached value]")
return true, nmap.registry["nbstat_names_" .. host], nmap.registry["nbstat_statistics_" .. host] return true, nmap.registry["nbstat_names_" .. host], nmap.registry["nbstat_statistics_" .. host]
end end

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +1,75 @@
id = "MSRPC: List of domains" id = "MSRPC: List of domains"
description = [[ description = [[ Attempts to enumerate domains on a system, along with their policies. This will likely
Attempts to enumerate domains on a system, along with their policies. This will only work without credentials against Windows 2000.
likely only work without credentials against Windows 2000.
After the initial bind() to SAMR, the sequence of calls is:\n After the initial bind() to SAMR, the sequence of calls is:
Connect4() -- get a connect_handle\n * Connect4() -- get a connect_handle
EnumDomains() -- get a list of the domains (stop here if you just want the names)\n * EnumDomains() -- get a list of the domains (stop here if you just want the names)
QueryDomain() -- get the sid for the domain\n * QueryDomain() -- get the sid for the domain
OpenDomain() -- get a handle for each domain\n * OpenDomain() -- get a handle for each domain
QueryDomainInfo2() -- get the domain information\n * QueryDomainInfo2() -- get the domain information
* QueryDomainUsers() -- get a list of the users in the domain
]] ]]
--- ---
-- @usage --@usage
-- nmap --script smb-enumdomains.nse -p445 <host>\n -- nmap --script smb-enumdomains.nse -p445 <host>
-- sudo nmap -sU -sS --script smb-enumdomains.nse -p U:137,T:139 <host>\n -- sudo nmap -sU -sS --script smb-enumdomains.nse -p U:137,T:139 <host>
--
--@output
-- Host script results:
-- | MSRPC: List of domains:
-- | Domain: LOCALSYSTEM
-- | |_ SID: S-1-5-21-2956463495-2656032972-1271678565
-- | |_ Users: Administrator, Guest, SUPPORT_388945a0
-- | |_ Creation time: 2007-11-26 15:24:04
-- | |_ Passwords: min length: 11 characters; min age: 5 days; max age: 63 days
-- | |_ Password lockout: 3 attempts in under 15 minutes will lock the account until manually reset
-- | |_ Password history : 5 passwords
-- | |_ Password properties:
-- | |_ Password complexity requirements exist
-- | |_ Administrator account cannot be locked out
-- | Domain: Builtin
-- | |_ SID: S-1-5-32
-- | |_ Users:
-- | |_ Creation time: 2007-11-26 15:24:04
-- | |_ Passwords: min length: n/a; min age: n/a; max age: 42 days
-- | |_ Account lockout disabled
-- | |_ Password properties:
-- | |_ Password complexity requirements do not exist
-- |_ |_ Administrator account cannot be locked out
--
--@args smbusername The SMB username to log in with. The form DOMAIN\username and username@DOMAIN
-- are NOT understood. To set a domain, use the smbdomain argument.
--@args smbdomain The domain to log in with. If you aren't in a domained environment, then anything
-- will (should?) be accepted by the server.
--@args smbpassword The password to connect with. Be cautious with this, since some servers will lock
-- accounts if the incorrect password is given (although it's rare for the
-- 'administrator' account to be lockoutable, in the off chance that it is, you could
-- get yourself in trouble).
--@args smbhash A password hash to use when logging in. This is given as a single hex string (32
-- characters) or a pair of hex strings (2 x 32 characters, optionally separated by a
-- single character). These hashes are the Lanman or NTLM hash of the user's password,
-- and are stored by systems, on the harddrive or memory. They can be retrived from memory
-- using the fgdump or pwdump tools.
--@args smbguest If this is set to 'true' or '1', a 'guest' login will be attempted if the normal one
-- fails. This should be harmless, but I thought I would disable it by default anyway
-- because I'm not entirely sure of any possible consequences.
--@args smbtype The type of SMB authentication to use. By default, NTLMv1 is used, which is a pretty
-- decent compromise between security and compatibility. If you are paranoid, you might
-- want to use 'v2' or 'lmv2' for this (actually, if you're paranoid, you should be
-- avoiding this protocol altogether :P). If you're using an extremely old system, you
-- might need to set this to 'v1' or 'lm', which are less secure but more compatible.
--
-- If you want finer grained control, these are the possible options:
-- * v1 -- Sends LMv1 and NTLMv1
-- * LMv1 -- Sends LMv1 only
-- * NTLMv1 -- Sends NTLMv1 only (default)
-- * v2 -- Sends LMv2 and NTLMv2
-- * LMv2 -- Sends LMv2 only
-- --
-- @output
-- Host script results:\n
-- | MSRPC: List of domains:\n
-- | Domain: TEST1\n
-- | |_ SID: S-1-5-21-1060284298-842925246-839522115\n
-- | |_ Users: Administrator, ASPNET, Guest, Ron, test\n
-- | |_ Creation time: 2006-10-17 15:35:07\n
-- | |_ Min password length: 0 characters\n
-- | |_ Max password age: 10675199 days\n
-- | |_ Min password age: 0 days\n
-- | |_ Password history length: 0 passwords\n
-- | |_ Lockout threshold: 0 login attempts\n
-- | |_ Lockout duration: 60 minutes\n
-- | |_ Lockout window: 60 minutes\n
-- | |_ Password properties: \n
-- | |_ Password complexity requirements do not exist\n
-- |_ |_ Administrator account cannot be locked out\n
----------------------------------------------------------------------- -----------------------------------------------------------------------
description = "Tries calling the EnumDomains() and QueryDomainInfo2() RPC function to obtain a list of domains/policies."
author = "Ron Bowes" author = "Ron Bowes"
copyright = "Ron Bowes" copyright = "Ron Bowes"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html" license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
@@ -58,123 +93,201 @@ end
action = function(host) action = function(host)
local response = " \n" local response = " \n"
local status, socket local status, smbstate
local uid, tid, fid
-- Create the SMB session -- Create the SMB session
status, socket, uid, tid, fid = msrpc.start_smb(host, msrpc.SAMR_PATH) status, smbstate = msrpc.start_smb(host, msrpc.SAMR_PATH)
if(status == false) then if(status == false) then
return "ERROR: " .. socket if(nmap.debugging() > 0) then
return "ERROR: " .. smbstate
else
return nil
end
end end
-- Bind to SAMR service -- Bind to SAMR service
status, bind_result = msrpc.bind(socket, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil, uid, tid, fid) status, bind_result = msrpc.bind(smbstate, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return "ERROR: " .. bind_result if(nmap.debugging() > 0) then
return "ERROR: " .. bind_result
else
return nil
end
end end
-- Call connect4() -- Call connect4()
status, connect4_result = msrpc.samr_connect4(socket, host.ip, uid, tid, fid) status, connect4_result = msrpc.samr_connect4(smbstate, host.ip)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return "ERROR: " .. connect4_result if(nmap.debugging() > 0) then
return "ERROR: " .. connect4_result
else
return nil
end
end end
-- Save the connect_handle -- Save the connect_handle
connect_handle = connect4_result['connect_handle'] connect_handle = connect4_result['connect_handle']
-- Call EnumDomains() -- Call EnumDomains()
status, enumdomains_result = msrpc.samr_enumdomains(socket, connect_handle, uid, tid, fid) status, enumdomains_result = msrpc.samr_enumdomains(smbstate, connect_handle)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return "ERROR: " .. enumdomains_result if(nmap.debugging() > 0) then
return "ERROR: " .. enumdomains_result
else
return nil
end
end end
-- If no domanis were returned, print an error (I don't expect this will actually happen) -- If no domanis were returned, print an error (I don't expect this will actually happen)
if(#enumdomains_result['domains'] == 0) then if(#enumdomains_result['domains'] == 0) then
return "ERROR: Couldn't find any domains to check" if(nmap.debugging() > 0) then
return "ERROR: Couldn't find any domains to check"
else
return nil
end
end end
for i = 1, #enumdomains_result['domains'], 1 do for i = 1, #enumdomains_result['domains'], 1 do
local domain = enumdomains_result['domains'][i] local domain = enumdomains_result['domains'][i]
-- We don't care about the 'builtin' domain local sid
if(domain ~= 'Builtin') then local domain_handle
local sid
local domain_handle
-- Call LookupDomain() -- Call LookupDomain()
status, lookupdomain_result = msrpc.samr_lookupdomain(socket, connect_handle, domain, uid, tid, fid) status, lookupdomain_result = msrpc.samr_lookupdomain(smbstate, connect_handle, domain)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
if(nmap.debugging() > 0) then
return "ERROR: " .. lookupdomain_result return "ERROR: " .. lookupdomain_result
else
return nil
end end
end
-- Save the sid -- Save the sid
sid = lookupdomain_result['sid'] sid = lookupdomain_result['sid']
-- Call OpenDomain() -- Call OpenDomain()
status, opendomain_result = msrpc.samr_opendomain(socket, connect_handle, sid, uid, tid, fid) status, opendomain_result = msrpc.samr_opendomain(smbstate, connect_handle, sid)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
if(nmap.debugging() > 0) then
return "ERROR: " .. opendomain_result return "ERROR: " .. opendomain_result
else
return nil
end end
end
-- Save the domain handle -- Save the domain handle
domain_handle = opendomain_result['domain_handle'] domain_handle = opendomain_result['domain_handle']
-- Call QueryDomainInfo2() to get domain properties. We call these for three types == 1, 8, and 12, since those return -- Call QueryDomainInfo2() to get domain properties. We call these for three types == 1, 8, and 12, since those return
-- the most useful information. -- the most useful information.
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(socket, domain_handle, 1, uid, tid, fid) status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 1)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return "ERROR: " .. querydomaininfo2_result if(nmap.debugging() > 0) then
end
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(socket, domain_handle, 8, uid, tid, fid, querydomaininfo2_result)
if(status == false) then
msrpc.stop_smb(socket, uid, tid)
return "ERROR: " .. querydomaininfo2_result
end
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(socket, domain_handle, 12, uid, tid, fid, querydomaininfo2_result)
if(status == false) then
msrpc.stop_smb(socket, uid, tid)
return "ERROR: " .. querydomaininfo2_result return "ERROR: " .. querydomaininfo2_result
else
return nil
end end
end
-- Call EnumDomainUsers() to get users status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 8, querydomaininfo2_result)
status, enumdomainusers_result = msrpc.samr_enumdomainusers(socket, domain_handle, uid, tid, fid) if(status == false) then
if(status == false) then msrpc.stop_smb(smbstate)
msrpc.stop_smb(socket, uid, tid) if(nmap.debugging() > 0) then
return "ERROR: " .. querydomaininfo2_result
else
return nil
end
end
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 12, querydomaininfo2_result)
if(status == false) then
msrpc.stop_smb(smbstate)
if(nmap.debugging() > 0) then
return "ERROR: " .. querydomaininfo2_result
else
return nil
end
end
-- Call EnumDomainUsers() to get users
status, enumdomainusers_result = msrpc.samr_enumdomainusers(smbstate, domain_handle)
if(status == false) then
msrpc.stop_smb(smbstate)
if(nmap.debugging() > 0) then
return "ERROR: " .. enumdomainusers_result return "ERROR: " .. enumdomainusers_result
else
return nil
end end
end
-- Close the domain handle -- Close the domain handle
msrpc.samr_close(socket, domain_handle, uid, tid, fid) msrpc.samr_close(smbstate, domain_handle)
-- Finally, fill in the response! -- Finally, fill in the response!
response = response .. string.format("Domain: %s\n", domain) response = response .. string.format("Domain: %s\n", domain)
response = response .. string.format(" |_ SID: %s\n", msrpc.sid_to_string(lookupdomain_result['sid'])) response = response .. string.format(" |_ SID: %s\n", msrpc.sid_to_string(lookupdomain_result['sid']))
response = response .. string.format(" |_ Users: %s\n", stdnse.strjoin(", ", enumdomainusers_result['names'])) response = response .. string.format(" |_ Users: %s\n", stdnse.strjoin(", ", enumdomainusers_result['names']))
response = response .. string.format(" |_ Creation time: %s\n", querydomaininfo2_result['create_date']) response = response .. string.format(" |_ Creation time: %s\n", querydomaininfo2_result['create_date'])
response = response .. string.format(" |_ Min password length: %d characters\n", querydomaininfo2_result['min_password_length'])
response = response .. string.format(" |_ Max password age: %d days\n", querydomaininfo2_result['max_password_age']) -- Password characteristics
response = response .. string.format(" |_ Min password age: %d days\n", querydomaininfo2_result['min_password_age']) local min_password_length = querydomaininfo2_result['min_password_length']
response = response .. string.format(" |_ Password history length: %d passwords\n", querydomaininfo2_result['password_history_length']) local max_password_age = querydomaininfo2_result['max_password_age']
response = response .. string.format(" |_ Lockout threshold: %d login attempts\n", querydomaininfo2_result['lockout_threshold']) local min_password_age = querydomaininfo2_result['min_password_age']
response = response .. string.format(" |_ Lockout duration: %d minutes\n", querydomaininfo2_result['lockout_duration'])
response = response .. string.format(" |_ Lockout window: %d minutes\n", querydomaininfo2_result['lockout_window']) if(min_password_length > 0) then
if(#querydomaininfo2_result['password_properties_list'] > 0) then min_password_length = string.format("%d characters", min_password_length)
response = response .. " |_ Password properties: \n |_ " .. stdnse.strjoin("\n |_ ", querydomaininfo2_result['password_properties_list']) .. "\n" else
end min_password_length = "n/a"
end
if(max_password_age > 0 and max_password_age < 5000) then
max_password_age = string.format("%d days", max_password_age)
else
max_password_age = "n/a"
end
if(min_password_age > 0) then
min_password_age = string.format("%d days", min_password_age)
else
min_password_age = "n/a"
end
response = response .. string.format(" |_ Passwords: min length: %s; min age: %s; max age: %s\n", min_password_length, min_password_age, max_password_age)
local lockout_duration = querydomaininfo2_result['lockout_duration']
if(lockout_duration < 0) then
lockout_duration = string.format("for %d minutes", querydomaininfo2_result['lockout_duration'])
else
lockout_duration = "until manually reset"
end
if(querydomaininfo2_result['lockout_threshold'] > 0) then
response = response .. string.format(" |_ Password lockout: %d attempts in under %d minutes will lock the account %s\n", querydomaininfo2_result['lockout_threshold'], querydomaininfo2_result['lockout_window'], lockout_duration)
else
response = response .. string.format(" |_ Account lockout disabled\n")
end
if(querydomaininfo2_result['password_history_length']) > 0 then
response = response .. string.format(" |_ Password history : %d passwords\n", querydomaininfo2_result['password_history_length'])
end
if(#querydomaininfo2_result['password_properties_list'] > 0) then
response = response .. " |_ Password properties: \n |_ " .. stdnse.strjoin("\n |_ ", querydomaininfo2_result['password_properties_list']) .. "\n"
end end
end end
-- Close the connect handle -- Close the connect handle
msrpc.samr_close(socket, connect_handle, uid, tid, fid) msrpc.samr_close(smbstate, connect_handle)
-- Close the SMB session -- Close the SMB session
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return response return response

View File

@@ -0,0 +1,349 @@
id = "MSRPC: NetSessEnum()"
description = [[
Enumerates the users logged into a system either locally, through a remote desktop client (terminal
services), or through a SMB share.
Enumerating the local and terminal services users is done by reading the remote registry. Keys under
HKEY_USERS are SIDs that represent the currently logged in users, and those SIDs can be converted
to proper names by using the LsaLookupSids() function. Doing this requires any access higher than
anonymous (guests, users, or administrators are all able to perform this request on the operating
systems I tested).
Enumerating SMB connections is done using the srvsvc.netsessenum() function, which returns who's
logged in, when they logged in, and how long they've been idle for. Unfortunately, I couldn't find
a way to get the user's domain with this function, so the domain isn't printed. The level of access
required for this varies between Windows versions, but in Windows 2000 anybody (including the
anonymous account) can access this, and in Windows 2003 a user or administrator account is
required.
Since both of these are related to users being logged into the server, it seemed logical to combine
them into a single script.
I learned the idea and technique for this from sysinternals' tool, PsLoggedOn.exe. I use similar
function calls to what they use, so thanks go out to them. Thanks also to Matt, for giving me the
idea to write this one.
]]
---
--@usage
-- nmap --script smb-enumsessions.nse -p445 <host>
-- sudo nmap -sU -sS --script smb-enumsessions.nse -p U:137,T:139 <host>
--
--@output
-- Host script results:
-- | MSRPC: NetSessEnum():
-- | Users logged in:
-- | |_ TESTBOX\Administrator since 2008-10-21 08:17:14
-- | |_ DOMAIN\rbowes since 2008-10-20 09:03:23
-- | Active SMB Sessions:
-- |_ |_ ADMINISTRATOR is connected from 10.100.254.138 for [just logged in, it's probably you], idle for [not idle]
--
--@args smbusername The SMB username to log in with. The form DOMAIN\username and username@DOMAIN
-- are NOT understood. To set a domain, use the smbdomain argument.
--@args smbdomain The domain to log in with. If you aren't in a domained environment, then anything
-- will (should?) be accepted by the server.
--@args smbpassword The password to connect with. Be cautious with this, since some servers will lock
-- accounts if the incorrect password is given (although it's rare for the
-- 'administrator' account to be lockoutable, in the off chance that it is, you could
-- get yourself in trouble).
--@args smbhash A password hash to use when logging in. This is given as a single hex string (32
-- characters) or a pair of hex strings (2 x 32 characters, optionally separated by a
-- single character). These hashes are the Lanman or NTLM hash of the user's password,
-- and are stored by systems, on the harddrive or memory. They can be retrived from memory
-- using the fgdump or pwdump tools.
--@args smbguest If this is set to 'true' or '1', a 'guest' login will be attempted if the normal one
-- fails. This should be harmless, but I thought I would disable it by default anyway
-- because I'm not entirely sure of any possible consequences.
--@args smbtype The type of SMB authentication to use. By default, NTLMv1 is used, which is a pretty
-- decent compromise between security and compatibility. If you are paranoid, you might
-- want to use 'v2' or 'lmv2' for this (actually, if you're paranoid, you should be
-- avoiding this protocol altogether :P). If you're using an extremely old system, you
-- might need to set this to 'v1' or 'lm', which are less secure but more compatible.
--
-- If you want finer grained control, these are the possible options:
-- * v1 -- Sends LMv1 and NTLMv1
-- * LMv1 -- Sends LMv1 only
-- * NTLMv1 -- Sends NTLMv1 only (default)
-- * v2 -- Sends LMv2 and NTLMv2
-- * LMv2 -- Sends LMv2 only
--
-----------------------------------------------------------------------
id = "MSRPC: NetSessEnum()"
description = "Tries calling the NetSessEnum() RPC function to get a list of active sessions"
author = "Ron Bowes"
copyright = "Ron Bowes"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery","intrusive"}
require 'msrpc'
require 'smb'
require 'stdnse'
hostrule = function(host)
local port = smb.get_port(host)
if(port == nil) then
return false
else
return true
end
end
---Attempts to enumerate the shares on a remote system using MSRPC calls. This will likely fail
-- against a modern system, but will succeed against Windows 2000.
--
--@param host The host object.
--@return (status, result) If status is false, result is an error string. Otherwise, result is
-- a list of all shares on a system.
local function srvsvc_enum_sessions(host)
local i
local status, smbstate
local bind_result, netsessenum_result
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to SRVSVC service
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Call netsessenum
status, netsessenum_result = msrpc.srvsvc_netsessenum(smbstate, host.ip)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, netsessenum_result
end
-- Stop the SMB session
msrpc.stop_smb(smbstate)
return true, netsessenum_result['sessions']
end
---Enumerates the users logged in locally (or through terminal services) by using functions
-- that access the registry. To perform this check, guest access or higher is required.
--
--@param host The host object.
--@return An array of tables, each table representing a user and containing values for 'name', 'domain', and 'changed_date' (representing
-- when they logged in).
local function winreg_enum_rids(host)
local i, j
local elements = {}
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to WINREG service
status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
status, openhku_result = msrpc.winreg_openhku(smbstate)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, openhku_result
end
-- Loop through the keys under HKEY_USERS and grab the names
i = 0
repeat
status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], i)
if(status == true) then
local status, openkey_result
local element = {}
element['name'] = enumkey_result['name']
element['sid'] = msrpc.string_to_sid(enumkey_result['name'])
-- To get the time the user logged in, we check the 'Volatile Environment' key
status, openkey_result = msrpc.winreg_openkey(smbstate, openhku_result['handle'], element['name'] .. "\\Volatile Environment")
if(status ~= false) then
local queryinfokey_result, closekey_result
-- Query the info about this key. The response will tell us when the user logged into the server.
status, queryinfokey_result = msrpc.winreg_queryinfokey(smbstate, openkey_result['handle'])
if(status == false) then
msrpc.stop_smb(smbstate)
return false, queryinfokey_result
end
status, closekey_result = msrpc.winreg_closekey(smbstate, openkey_result['handle'])
if(status == false) then
msrpc.stop_smb(smbstate)
return false, closekey_result
end
element['changed_date'] = queryinfokey_result['last_changed_date']
elements[#elements + 1] = element
end
end
i = i + 1
until status ~= true
status, closekey_result = msrpc.winreg_closekey(smbstate, openhku_result['handle'])
if(status == false) then
msrpc.stop_smb(smbstate)
return false, closekey_result
end
msrpc.stop_smb(smbstate)
-- Start a new SMB session
status, smbstate = msrpc.start_smb(host, msrpc.LSA_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to LSA service
status, bind_result = msrpc.bind(smbstate, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Get a policy handle
status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, openpolicy2_result
end
-- Convert the RIDs to names
local results = {}
stdnse.print_debug(3, "MSRPC: Found %d SIDs that might be logged in", #elements)
for i = 1, #elements, 1 do
if(elements[i]['sid'] ~= nil) then
-- The RID is the last subauthority
local rid = elements[i]['sid']['subauthorities'][elements[i]['sid']['count']]
stdnse.print_debug(3, "MSRPC: Found an actual RID: %d", rid)
-- The server is the rest of the SID, so remove the last subauthority
elements[i]['sid']['subauthorities'][elements[i]['sid']['count']] = nil
elements[i]['sid']['count'] = elements[i]['sid']['count'] - 1
-- Look up the RID
stdnse.print_debug(3, "MSRPC: Looking up RID %s in SID %s", rid, msrpc.sid_to_string(elements[i]['sid']))
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], elements[i]['sid'], {rid})
if(status == false) then
-- It may not succeed, if it doesn't that's ok
stdnse.print_debug(3, "MSRPC: Lookup failed")
else
-- Create the result array
local result = {}
result['rid'] = rid
result['changed_date'] = elements[i]['changed_date']
-- Fill in the result from the response
if(lookupsids2_result['details'][1] == nil) then
result['name'] = "<unknown>"
result['domain'] = ""
else
result['name'] = lookupsids2_result['details'][1]['name']
result['type'] = lookupsids2_result['details'][1]['type']
result['domain'] = lookupsids2_result['domains'][1]['name']
end
if(result['type'] ~= 5) then -- Don't show "well known" accounts
-- Add it to the results
results[#results + 1] = result
end
end
end
end
-- Close the policy
msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle'])
-- Stop the session
msrpc.stop_smb(smbstate)
return true, results
end
action = function(host)
local response = " \n"
local status1, status2
-- Enumerate the logged in users
status1, users = winreg_enum_rids(host)
if(status1 == false) then
response = response .. "ERROR: Couldn't enumerate login sessions: " .. users .. "\n"
else
response = response .. "Users logged in:\n"
if(#users == 0) then
response = response .. "|_ <nobody>\n"
else
for i = 1, #users, 1 do
response = response .. string.format("|_ %s\\%s since %s\n", users[i]['domain'], users[i]['name'], users[i]['changed_date'])
end
end
end
-- Get the connected sessions
status2, sessions = srvsvc_enum_sessions(host)
if(status2 == false) then
response = response .. "ERROR: Couldn't enumerate network sessions: " .. sessions .. "\n"
else
response = response .. "Active SMB Sessions:\n"
if(#sessions == 0) then
response = response .. "|_ <none>\n"
else
-- Format the result
for i = 1, #sessions, 1 do
local active = sessions[i]['active']
if(active == 0) then
active = "[just logged in, it's probably you]"
elseif(active > 60 * 60 * 24) then
active = string.format("%dd%dh%02dm%02ds", active / (60*60*24), (active % (60*60*24)) / 3600, (active % 3600) / 60, active % 60)
elseif(active > 60 * 60) then
active = string.format("%dh%02dm%02ds", active / 3600, (active % 3600) / 60, active % 60)
else
active = string.format("%02dm%02ds", active / 60, active % 60)
end
local idle = sessions[i]['idle']
if(idle == 0) then
idle = "[not idle]"
elseif(idle > 60 * 60 * 24) then
idle = string.format("%dd%dh%02dm%02ds", idle / (60*60*24), (idle % (60*60*24)) / 3600, (idle % 3600) / 60, idle % 60)
elseif(idle > 60 * 60) then
idle = string.format("%dh%02dm%02ds", idle / 3600, (idle % 3600) / 60, idle % 60)
else
idle = string.format("%02dm%02ds", idle / 60, idle % 60)
end
response = response .. string.format("|_ %s is connected from %s for %s, idle for %s\n", sessions[i]['user'], sessions[i]['client'], active, idle)
end
end
end
if(status1 == false and status2 == false) then
if(nmap.debugging() > 0) then
return response
else
return nil
end
else
return response
end
end

View File

@@ -1,25 +1,89 @@
id = "MSRPC: NetShareEnumAll()" id = "MSRPC: List of shares"
description = [[ description = [[
Attempts to list shares using the srvsvc.NetShareEnumAll() MSRPC function. This Attempts to list shares using the srvsvc.NetShareEnumAll() MSRPC function, then
will likely only work anonymously against Windows 2000. retrieve more information about each share using srvsvc.NetShareGetInfo(). Running
NetShareEnumAll() will work anonymously on Windows 2000, and requires a user level
account on any other Windows version. Calling NetShareGetInfo() requires an
administrator account on every version of Windows I tested.
There isn't a whole lot to say about this one. The sequence of calls after Although NetShareEnumAll() is restricted on certain systems, actually connecting to
the initial bind() is:\n a share to check if it exists will always work. So, if NetShareEnumAll() fails, a
NetShareEnumAll() list of common shares will be attempted.
\n\n
Since NetShareEnumAll() only works anonymously, if it fails this will check After a list of shares is found, whether or not it's complete, we attempt to connect
a handful of common shares. to each of them anonymously, which lets us divide them into "anonymous" and
\n\n "restricted".
Once it has a list of shares, whether it was pulled over MSRPC or guessed,
we attempt to connect to each of them with a standard smb tree_connect request When possible, once the list of shares is determined, NetShareGetInfo() is called
over a null session. We record which ones succeeded and failed (that is, which to get additional information on the share. Odds are this will fail, unless we're
shares allowed for anonymous access). doing an authenticated test.
]] ]]
--- ---
--@usage --@usage
-- nmap --script smb-enumshares.nse -p445 <host>\n -- nmap --script smb-enumshares.nse -p445 <host>
-- sudo nmap -sU -sS --script smb-enumshares.nse -p U:137,T:139 <host> -- sudo nmap -sU -sS --script smb-enumshares.nse -p U:137,T:139 <host>
--
--@output
-- Standard:
-- | MSRPC: NetShareEnumAll():
-- | Anonymous shares: IPC$
-- |_ Restricted shares: F$, ADMIN$, C$
--
-- Verbose:
-- Host script results:
-- | MSRPC: NetShareEnumAll():
-- | Anonymous shares:
-- | IPC$
-- | |_ Type: STYPE_IPC_HIDDEN
-- | |_ Comment: Remote IPC
-- | |_ Users: 1, Max: <unlimited>
-- | |_ Path:
-- | test
-- | |_ Type: STYPE_DISKTREE
-- | |_ Comment: This is a test share, with a maximum of 7 users
-- | |_ Users: 0, Max: 7
-- | |_ Path: C:\Documents and Settings\Ron\Desktop\test
-- | Restricted shares:
-- | ADMIN$
-- | |_ Type: STYPE_DISKTREE_HIDDEN
-- | |_ Comment: Remote Admin
-- | |_ Users: 0, Max: <unlimited>
-- | |_ Path: C:\WINNT
-- | C$
-- | |_ Type: STYPE_DISKTREE_HIDDEN
-- | |_ Comment: Default share
-- | |_ Users: 0, Max: <unlimited>
-- |_ |_ Path: C:\
--
--@args smbusername The SMB username to log in with. The form DOMAIN\username and username@DOMAIN
-- are NOT understood. To set a domain, use the smbdomain argument.
--@args smbdomain The domain to log in with. If you aren't in a domained environment, then anything
-- will (should?) be accepted by the server.
--@args smbpassword The password to connect with. Be cautious with this, since some servers will lock
-- accounts if the incorrect password is given (although it's rare for the
-- 'administrator' account to be lockoutable, in the off chance that it is, you could
-- get yourself in trouble).
--@args smbhash A password hash to use when logging in. This is given as a single hex string (32
-- characters) or a pair of hex strings (2 x 32 characters, optionally separated by a
-- single character). These hashes are the Lanman or NTLM hash of the user's password,
-- and are stored by systems, on the harddrive or memory. They can be retrived from memory
-- using the fgdump or pwdump tools.
--@args smbguest If this is set to 'true' or '1', a 'guest' login will be attempted if the normal one
-- fails. This should be harmless, but I thought I would disable it by default anyway
-- because I'm not entirely sure of any possible consequences.
--@args smbtype The type of SMB authentication to use. By default, NTLMv1 is used, which is a pretty
-- decent compromise between security and compatibility. If you are paranoid, you might
-- want to use 'v2' or 'lmv2' for this (actually, if you're paranoid, you should be
-- avoiding this protocol altogether :P). If you're using an extremely old system, you
-- might need to set this to 'v1' or 'lm', which are less secure but more compatible.
--
-- If you want finer grained control, these are the possible options:
-- * v1 -- Sends LMv1 and NTLMv1
-- * LMv1 -- Sends LMv1 only
-- * NTLMv1 -- Sends NTLMv1 only (default)
-- * v2 -- Sends LMv2 and NTLMv2
-- * LMv2 -- Sends LMv2 only
--
----------------------------------------------------------------------- -----------------------------------------------------------------------
author = "Ron Bowes" author = "Ron Bowes"
@@ -51,31 +115,31 @@ end
-- a list of all shares on a system. -- a list of all shares on a system.
local function samr_enum_shares(host) local function samr_enum_shares(host)
local status, socket, uid, tid, fid local status, smbstate
local bind_result, netshareenumall_result local bind_result, netshareenumall_result
-- Create the SMB session -- Create the SMB session
status, socket, uid, tid, fid = msrpc.start_smb(host, msrpc.SRVSVC_PATH) status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
if(status == false) then if(status == false) then
return false, socket return false, smbstate
end end
-- Bind to SRVSVC service -- Bind to SRVSVC service
status, bind_result = msrpc.bind(socket, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil, uid, tid, fid) status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then if(status == false) then
smb.stop(socket) smb.stop(smbstate)
return false, bind_result return false, bind_result
end end
-- Call netsharenumall -- Call netsharenumall
status, netshareenumall_result = msrpc.srvsvc_netshareenumall(socket, host.ip, uid, tid, fid) status, netshareenumall_result = msrpc.srvsvc_netshareenumall(smbstate, host.ip)
if(status == false) then if(status == false) then
smb.stop(socket) smb.stop(smbstate)
return false, netshareenumall_result return false, netshareenumall_result
end end
-- Stop the SMB session -- Stop the SMB session
smb.stop(socket, uid, tid) smb.stop(smbstate)
return true, netshareenumall_result['shares'] return true, netshareenumall_result['shares']
end end
@@ -88,28 +152,29 @@ end
--@return (allowed_shares, denied_shares) Lists of shares we can and can't access, --@return (allowed_shares, denied_shares) Lists of shares we can and can't access,
-- but all of which exist. -- but all of which exist.
function check_shares(host, shares) function check_shares(host, shares)
local smbstate
local i local i
local allowed_shares = {} local allowed_shares = {}
local denied_shares = {} local denied_shares = {}
-- Begin the SMB session -- Begin the SMB session
status, socket = smb.start(host) status, smbstate = smb.start(host)
if(status == false) then if(status == false) then
return false, socket return false, smbstate
end end
-- Negotiate the protocol -- Negotiate the protocol
status, negotiate_result = smb.negotiate_protocol(socket) status, err = smb.negotiate_protocol(smbstate)
if(status == false) then if(status == false) then
smb.stop(socket) smb.stop(smbstate)
return false, negotiate_result return false, err
end end
-- Start up a null session -- Start up a null session
status, session_result = smb.start_session(socket, "", negotiate_result['session_key'], negotiate_result['capabilities']) status, err = smb.start_session(smbstate, "", "", "", "", "LM")
if(status == false) then if(status == false) then
smb.stop(socket) smb.stop(smbstate)
return false, session_result return false, err
end end
-- Connect to the shares -- Connect to the shares
@@ -121,30 +186,65 @@ function check_shares(host, shares)
-- Try connecting to the tree -- Try connecting to the tree
stdnse.print_debug(3, "EnumShares: Testing share %s", share) stdnse.print_debug(3, "EnumShares: Testing share %s", share)
status, tree_result = smb.tree_connect(socket, share, session_result['uid']) status, err = smb.tree_connect(smbstate, share)
-- If it fails, checkwhy -- If it fails, checkwhy
if(status == false) then if(status == false) then
-- If the result was ACCESS_DENIED, record it -- If the result was ACCESS_DENIED, record it
if(tree_result == 0xc0000022 or tree_result == 'NT_STATUS_ACCESS_DENIED') then if(err == 0xc0000022 or err == 'NT_STATUS_ACCESS_DENIED') then
stdnse.print_debug(3, "EnumShares: Access was denied") stdnse.print_debug(3, "EnumShares: Access was denied")
denied_shares[#denied_shares + 1] = shares[i] denied_shares[#denied_shares + 1] = shares[i]
else else
stdnse.print_debug(3, "EnumShares: Share didn't pan out: %s", tree_result) stdnse.print_debug(3, "EnumShares: Share didn't pan out: %s", err)
end end
else else
-- Add it to allowed shares -- Add it to allowed shares
stdnse.print_debug(3, "EnumShares: Access was granted") stdnse.print_debug(3, "EnumShares: Access was granted")
allowed_shares[#allowed_shares + 1] = shares[i] allowed_shares[#allowed_shares + 1] = shares[i]
smb.tree_disconnect(socket, session_result['uid'], tree_result['tid']) smb.tree_disconnect(smbstate)
end end
end end
-- Log off the user -- Log off the user
smb.stop(socket, session_result['uid']) smb.stop(smbstate)
return allowed_shares, denied_shares return true, allowed_shares, denied_shares
end end
---Attempts to retrieve additional information about a share. Will fail unless we have
-- administrative access.
--
--@param host The host object.
--@return (status, result) If status is false, result is an error string. Otherwise, result is
-- a list of all shares on a system.
local function get_share_info(host, name)
local status, smbstate
local response = {}
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to SRVSVC service
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then
smb.stop(smbstate)
return false, bind_result
end
-- Call NetShareGetInfo
status, netsharegetinfo_result = msrpc.srvsvc_netsharegetinfo(smbstate, host.ip, name, 2)
if(status == false) then
smb.stop(smbstate)
return false, netsharegetinfo_result
end
smb.stop(smbstate)
return true, netsharegetinfo_result
end
action = function(host) action = function(host)
local result, shared local result, shared
@@ -158,7 +258,10 @@ action = function(host)
-- If that failed, try doing it with brute force. This almost certainly won't find everything, but it's the -- If that failed, try doing it with brute force. This almost certainly won't find everything, but it's the
-- best we can do. -- best we can do.
if(result == false) then if(result == false) then
response = response .. string.format("Couldn't enum all shares, checking for common ones (%s)\n", shares) if(nmap.debugging() > 0) then
response = response .. string.format("Couldn't enum all shares, checking for common ones (%s)\n", shares)
end
-- Take some common share names I've seen -- Take some common share names I've seen
shares = {"IPC$", "ADMIN$", "TEST", "TEST$", "HOME", "HOME$"} shares = {"IPC$", "ADMIN$", "TEST", "TEST$", "HOME", "HOME$"}
-- Try every alphabetic share, with and without a trailing '$' -- Try every alphabetic share, with and without a trailing '$'
@@ -169,9 +272,61 @@ action = function(host)
end end
-- Break them into anonymous/authenticated shares -- Break them into anonymous/authenticated shares
allowed, denied = check_shares(host, shares) status, allowed, denied = check_shares(host, shares)
return response .. string.format("Anonymous shares: %s\nRestricted shares: %s\n", stdnse.strjoin(", ", allowed), stdnse.strjoin(", ", denied)) if(status == false) then
if(nmap.debugging() > 0) then
return "ERROR: " .. allowed
else
return nil
end
end
if(result == false or nmap.verbosity() == 0) then
return response .. string.format("Anonymous shares: %s\nRestricted shares: %s\n", stdnse.strjoin(", ", allowed), stdnse.strjoin(", ", denied))
else
response = response .. string.format("Anonymous shares:\n")
for i = 1, #allowed, 1 do
local status, info = get_share_info(host, allowed[i])
response = response .. string.format(" %s\n", allowed[i])
if(status == false) then
stdnse.print_debug(2, "Error getting information for share %s: %s", allowed[i], info)
else
if(info['max_users'] == 0xFFFFFFFF) then
info['max_users'] = "<unlimited>"
end
response = response .. string.format(" |_ Type: %s\n", info['strtype'])
response = response .. string.format(" |_ Comment: %s\n", info['comment'])
response = response .. string.format(" |_ Users: %s, Max: %s\n", info['current_users'], info['max_users'])
response = response .. string.format(" |_ Path: %s\n", info['path'])
end
end
response = response .. string.format("Restricted shares:\n")
for i = 1, #denied, 1 do
local status, info = get_share_info(host, denied[i])
response = response .. string.format(" %s\n", denied[i])
if(status == false) then
stdnse.print_debug(2, "Error getting information for share %s: %s", denied[i], info)
else
if(info['max_users'] == 0xFFFFFFFF) then
info['max_users'] = "<unlimited>"
end
response = response .. string.format(" |_ Type: %s\n", info['strtype'])
response = response .. string.format(" |_ Comment: %s\n", info['comment'])
response = response .. string.format(" |_ Users: %s, Max: %s\n", info['current_users'], info['max_users'])
response = response .. string.format(" |_ Path: %s\n", info['path'])
end
end
return response
end
end end

View File

@@ -2,74 +2,148 @@ id = "MSRPC: List of user accounts"
description = [[ description = [[
Attempts to enumerate the users on a remote Windows system, with as much Attempts to enumerate the users on a remote Windows system, with as much
information as possible, through a variety of techniques (over SMB + MSRPC, information as possible, through a variety of techniques (over SMB + MSRPC,
which uses port 445 or 139). which uses port 445 or 139). Some functions in SAMR are used to enumerate
users, and some bruteforce guessing using LSA functions is attempted.
One technique used is calling the QueryDisplayInfo() function in the SAMR library.
If this succeeds, it will return a detailed list of users. This can be done
anonymously against Windows 2000, and with a user-level account on other Windows
versions (but not with a guest-level account).
To perform this test, the following functions are used:
* Bind() -- bind to the SAMR service
* Connect4() -- get a connect_handle
* EnumDomains() -- get a list of the domains
* QueryDomain() -- get the sid for the domain
* OpenDomain() -- get a handle for each domain
* QueryDisplayInfo() -- get the list of users in the domain
* Close() -- Close the domain handle
* Close() -- Close the connect handle
The advantage of this technique is that a lot of details are returned, including
the full name and description; the disadvantage is that it requires a user-level
account on every system except for Windows 2000. Additionally, it only pulls actual
user accounts, not groups or aliasts.
Will first attempt to call the QueryDisplayInfo() MSRPC function. If NULL
sessions are enabled, this will succeed and pull back a detailed list of users.
Unfortunately, this likely won't succeed unless we're scanning Windows 2000.
When this test is performed, the following MSRPC functions are called:\n
Bind() -- bind to the SAMR service\n
Connect4() -- get a connect_handle\n
EnumDomains() -- get a list of the domains\n
QueryDomain() -- get the sid for the domain\n
OpenDomain() -- get a handle for each domain\n
QueryDisplayInfo() -- get the list of users in the domain\n
Close() -- Close the domain handle\n
Close() -- Close the connect handle
\n\n
Credit goes out to the enum.exe program, the code I wrote for this is largely
due to packetlogs I took of its operations.
\n\n
Regardless of whether or not this succeeds, a second technique is used to pull Regardless of whether or not this succeeds, a second technique is used to pull
user accounts. This one is apparently successful against more machines, user accounts, called LSA bruteforcing. LSA bruteforcing can be done anonymously
although I haven't found a machine that this only works against. However, I did against Windows 2000, and requires a guest account or better on other systems.
find that this will turn up more users for certain systems (although I haven't It has the advantage of running with less permissions, and will also find more
figured out why). account types (ie, groups, aliases, etc). The disadvantages is that it returns
less information, and that, because it's a bruteforce, it's possible to miss
accounts.
\n\n \n\n
Each user on a Windows system has an RID. The RID of 500 is the Administrator This isn't a bruteforce in the common sense, however; it's a bruteforce of users'
account (even if it's renamed), 501 is the Guest account, and 1000+ are the RIDs. A user's RID is a value (generally 500, 501, or 1000+) that uniquely identifies
user accounts. This technique, which was originally used in the a user on a domain or system. An LSA function is exposed which lets us convert the RID
sid2user/user2sid programs, will attempt to convert common RID numbers to names (say, '1000') to the username (say, 'Ron'). So, the bruteforce will essentially try
to discover users. converting 1000 to a name, 1001, 1002, etc., until we think we're done.
\n\n \n\n
First, the SID of the server has to be determined. This is done by looking up I break the users into 5-RID groups, and check them individually (checking too many
any name present on the server using a technique like user2sid. For this code, at once causes problems). I continue checking until I reach 1100, and get an empty
we try and convert as many names as we can find -- all we need is one valid group. This probably isn't the most effective way, but it seems to work.
name for this to succeed. In this code, I use:\n It might be a good idea to modify this, in the future, with some more
- The computer name / domain name, returned in SMB_COM_NEGOTIATE\n intelligence. I performed a test on an old server with a lot of accounts,
- An nbstat query to get the server name and the currently loggeed in user\n and I got these results: 500, 501, 1000, 1030, 1031, 1053, 1054, 1055,
- Some common names ("administrator", "guest", and "test") 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1070,
1075, 1081, 1088, 1090. The jump from 1000 to 1030 is quite large and can easily
result in missing accounts, in an automated check.
\n\n
Before attempting this conversion, the SID of the server has to be determined.
The SID is determined by doing the reverse operation -- converting a name into
a RID. The name is determined by looking up any name present on the system.
In this script, I try looking up:
\n\n
<ul>
<li>The computer name / domain name, returned in SMB_COM_NEGOTIATE
<li>An nbstat query to get the server name and the currently loggeed in user
<li>Some common names ("administrator", "guest", and "test")
</ul>
\n\n \n\n
In theory, the computer name should be sufficient for this to always work, and In theory, the computer name should be sufficient for this to always work, and
the rest of the names are in there for good measure. so far has in my tests, but I included the rest of the names for good measure.
\n\n
Once that's completed, the RIDs 500 - 505 are requested, and any responses are
displayed. Then, starting at 1000, we take small groups of RIDs which are
requestd. I break them into smaller groups because if too many are requested at
once, we get a STATUS_BUFFER_OVERFLOW error. We try every RID up to 1100, then,
as soon as we get an empty group (5 RIDs in a row without a result), we stop.
\n\n
It might be a good idea to modify this, in the future, with some more
intelligence. For example, have it run until it get 5 groups in a row with no
results instead of going up to 1100. I performed a test on an old server we
have here with a lot of accounts, and I got these results: 500, 501, 1000,
1030, 1031, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063,
1064, 1065, 1066, 1067, 1070, 1075, 1081, 1088, 1090. The jump from 1000 to
1030 is quite large and can easily result in missing accounts.
\n\n
The disadvantage of using the user2sid/sid2user technique is that less
information is returned about the user.
\n\n \n\n
The names and details from both of these techniques are merged and displayed. The names and details from both of these techniques are merged and displayed.
If the output is verbose, then as many details as possible are displayed, If the output is verbose, then extra details. The output is ordered alphabetically.
otherwise only the list of usernames are displayed. The names are ordered \n\n
alphabetically. Credit goes out to the enum.exe, sid2user.exe, and user2sid.exe programs,
the code I wrote for this is largely based on the techniques used by them.
]] ]]
--- ---
-- @usage -- @usage
-- nmap --script smb-enumusers.nse -p445 <host>\n -- nmap --script smb-enumusers.nse -p445 <host>
-- sudo nmap -sU -sS --script smb-enumusers.nse -p U:137,T:139 <host> -- sudo nmap -sU -sS --script smb-enumusers.nse -p U:137,T:139 <host>
--
-- @output
-- Host script results:
-- | MSRPC: List of user accounts:
-- |_ TESTBOX\Administrator, EXTERNAL\DnsAdmins, TESTBOX\Guest, EXTERNAL\HelpServicesGroup, EXTERNAL\PARTNERS$, TESTBOX\SUPPORT_388945a0
--
-- Host script results:
-- | MSRPC: List of user accounts:
-- | Administrator
-- | |_ Type: User
-- | |_ Domain: LOCALSYSTEM
-- | |_ Full name: Built-in account for administering the computer/domain
-- | |_ Flags: Normal account, Password doesn't expire
-- | DnsAdmins
-- | |_ Type: Alias
-- | |_ Domain: EXTRANET
-- | EventViewer
-- | |_ Type: User
-- | |_ Domain: SHARED
-- | ProxyUsers
-- | |_ Type: Group
-- | |_ Domain: EXTRANET
-- | ComputerAccounts
-- | |_ Type: Group
-- | |_ Domain: EXTRANET
-- | Helpdesk
-- | |_ Type: Group
-- | |_ Domain: EXTRANET
-- | Guest
-- | |_ Type: User
-- | |_ Domain: LOCALSYSTEM
-- | |_ Full name: Built-in account for guest access to the computer/domain
-- | |_ Flags: Normal account, Disabled, Password not required, Password doesn't expire
-- | Staff
-- | |_ Type: Alias
-- | |_ Domain: LOCALSYSTEM
-- | Students
-- | |_ Type: Alias
-- |_ |_ Domain: LOCALSYSTEM
--
--
--@args smbusername The SMB username to log in with. The form DOMAIN\username and username@DOMAIN
-- are NOT understood. To set a domain, use the smbdomain argument.
--@args smbdomain The domain to log in with. If you aren't in a domained environment, then anything
-- will (should?) be accepted by the server.
--@args smbpassword The password to connect with. Be cautious with this, since some servers will lock
-- accounts if the incorrect password is given (although it's rare for the
-- 'administrator' account to be lockoutable, in the off chance that it is, you could
-- get yourself in trouble).
--@args smbhash A password hash to use when logging in. This is given as a single hex string (32
-- characters) or a pair of hex strings (2 x 32 characters, optionally separated by a
-- single character). These hashes are the Lanman or NTLM hash of the user's password,
-- and are stored by systems, on the harddrive or memory. They can be retrived from memory
-- using the fgdump or pwdump tools.
--@args smbguest If this is set to 'true' or '1', a 'guest' login will be attempted if the normal one
-- fails. This should be harmless, but I thought I would disable it by default anyway
-- because I'm not entirely sure of any possible consequences.
--@args smbtype The type of SMB authentication to use. By default, NTLMv1 is used, which is a pretty
-- decent compromise between security and compatibility. If you are paranoid, you might
-- want to use 'v2' or 'lmv2' for this (actually, if you're paranoid, you should be
-- avoiding this protocol altogether :P). If you're using an extremely old system, you
-- might need to set this to 'v1' or 'lm', which are less secure but more compatible.
--
-- If you want finer grained control, these are the possible options:
-- * v1 -- Sends LMv1 and NTLMv1
-- * LMv1 -- Sends LMv1 only
-- * NTLMv1 -- Sends NTLMv1 only (default)
-- * v2 -- Sends LMv2 and NTLMv2
-- * LMv2 -- Sends LMv2 only
--
----------------------------------------------------------------------- -----------------------------------------------------------------------
author = "Ron Bowes" author = "Ron Bowes"
@@ -100,29 +174,32 @@ end
-- array of tables. Each table contains a 'name', 'domain', 'fullname', 'rid', and 'description'. -- array of tables. Each table contains a 'name', 'domain', 'fullname', 'rid', and 'description'.
local function enum_samr(host) local function enum_samr(host)
stdnse.print_debug(3, "Entering enum_samr()")
local smbstate
local bind_result, connect4_result, enumdomains_result local bind_result, connect4_result, enumdomains_result
local connect_handle local connect_handle
local status, socket local status, smbstate
local uid, tid, fid
local response = {} local response = {}
-- Create the SMB session -- Create the SMB session
status, socket, uid, tid, fid = msrpc.start_smb(host, msrpc.SAMR_PATH) status, smbstate = msrpc.start_smb(host, msrpc.SAMR_PATH)
if(status == false) then if(status == false) then
return false, socket return false, smbstate
end end
-- Bind to SAMR service -- Bind to SAMR service
status, bind_result = msrpc.bind(socket, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil, uid, tid, fid) status, bind_result = msrpc.bind(smbstate, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return false, bind_result return false, bind_result
end end
-- Call connect4() -- Call connect4()
status, connect4_result = msrpc.samr_connect4(socket, host.ip, uid, tid, fid) status, connect4_result = msrpc.samr_connect4(smbstate, host.ip)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return false, connect4_result return false, connect4_result
end end
@@ -130,31 +207,32 @@ local function enum_samr(host)
connect_handle = connect4_result['connect_handle'] connect_handle = connect4_result['connect_handle']
-- Call EnumDomains() -- Call EnumDomains()
status, enumdomains_result = msrpc.samr_enumdomains(socket, connect_handle, uid, tid, fid) status, enumdomains_result = msrpc.samr_enumdomains(smbstate, connect_handle)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return false, enumdomains_result return false, enumdomains_result
end end
-- If no domains were returned, go back with an error -- If no domains were returned, go back with an error
if(#enumdomains_result['domains'] == 0) then if(#enumdomains_result['domains'] == 0) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return false, "Couldn't find any domains" return false, "Couldn't find any domains"
end end
-- Now, loop through the domains and find the users
for i = 1, #enumdomains_result['domains'], 1 do for i = 1, #enumdomains_result['domains'], 1 do
local domain = enumdomains_result['domains'][i] local domain = enumdomains_result['domains'][i]
-- We don't care about the 'builtin' domain -- We don't care about the 'builtin' domain, in all my tests it's empty
if(domain ~= 'Builtin') then if(domain ~= 'Builtin') then
local sid local sid
local domain_handle local domain_handle
local opendomain_result, querydisplayinfo_result local opendomain_result, querydisplayinfo_result
-- Call LookupDomain() -- Call LookupDomain()
status, lookupdomain_result = msrpc.samr_lookupdomain(socket, connect_handle, domain, uid, tid, fid) status, lookupdomain_result = msrpc.samr_lookupdomain(smbstate, connect_handle, domain)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return false, lookupdomain_result return false, lookupdomain_result
end end
@@ -162,9 +240,9 @@ local function enum_samr(host)
sid = lookupdomain_result['sid'] sid = lookupdomain_result['sid']
-- Call OpenDomain() -- Call OpenDomain()
status, opendomain_result = msrpc.samr_opendomain(socket, connect_handle, sid, uid, tid, fid) status, opendomain_result = msrpc.samr_opendomain(smbstate, connect_handle, sid)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return false, opendomain_result return false, opendomain_result
end end
@@ -172,28 +250,33 @@ local function enum_samr(host)
domain_handle = opendomain_result['domain_handle'] domain_handle = opendomain_result['domain_handle']
-- Call QueryDisplayInfo() -- Call QueryDisplayInfo()
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(socket, domain_handle, uid, tid, fid) status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return false, querydisplayinfo_result return false, querydisplayinfo_result
end end
-- Close the domain handle -- Close the domain handle
msrpc.samr_close(socket, domain_handle, uid, tid, fid) msrpc.samr_close(smbstate, domain_handle)
-- Finally, fill in the response! -- Finally, fill in the response!
for i = 1, #querydisplayinfo_result['details'], 1 do for i = 1, #querydisplayinfo_result['details'], 1 do
querydisplayinfo_result['details'][i]['domain'] = domain querydisplayinfo_result['details'][i]['domain'] = domain
-- All we get from this is users
querydisplayinfo_result['details'][i]['typestr'] = "User"
querydisplayinfo_result['details'][i]['source'] = "SAMR Enumeration"
response[#response + 1] = querydisplayinfo_result['details'][i] response[#response + 1] = querydisplayinfo_result['details'][i]
end end
end -- Checking for 'builtin' end -- Checking for 'builtin'
end -- Domain loop end -- Domain loop
-- Close the connect handle -- Close the connect handle
msrpc.samr_close(socket, connect_handle, uid, tid, fid) msrpc.samr_close(smbstate, connect_handle)
-- Stop the SAMR SMB -- Stop the SAMR SMB
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
stdnse.print_debug(3, "Leaving enum_samr()")
return true, response return true, response
end end
@@ -205,116 +288,126 @@ end
-- array of tables. Each table contains a 'name', 'domain', and 'rid'. -- array of tables. Each table contains a 'name', 'domain', and 'rid'.
local function enum_lsa(host) local function enum_lsa(host)
local status, socket local smbstate
local uid, tid, fid local status
local response = {} local response = {}
-- Create the SMB session stdnse.print_debug(3, "Entering enum_lsa()")
status, socket, uid, tid, fid, negotiate_result = msrpc.start_smb(host, msrpc.LSA_PATH)
if(status == false) then
return false, socket
end
-- Bind to LSA service -- Create the SMB session
status, bind_result = msrpc.bind(socket, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil, uid, tid, fid) status, smbstate = msrpc.start_smb(host, msrpc.LSA_PATH)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) return false, smbstate
return false, bind_result end
end
-- Open the LSA policy -- Bind to LSA service
status, openpolicy2_result = msrpc.lsa_openpolicy2(socket, host.ip, uid, tid, fid) status, bind_result = msrpc.bind(smbstate, msrpc.LSA_UUID, msrpc.LSA_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) msrpc.stop_smb(smbstate)
return false, openpolicy2_result return false, bind_result
end end
-- Start with some common names, as well as the name returned by the negotiate call -- Open the LSA policy
names = {"administrator", "guest", "test", negotiate_result['domain'], negotiate_result['server'] } status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, openpolicy2_result
end
-- Get the server's name from nbstat -- Start with some common names, as well as the name returned by the negotiate call
local result, server_name = netbios.get_server_name(host.ip) -- Vista doesn't like a 'null' after the server name, so fix that (TODO: the way I strip the null here feels hackish, is there a better way?)
if(result == true) then names = {"administrator", "guest", "test", smbstate['domain'], string.sub(smbstate['server'], 1, #smbstate['server'] - 1) }
names[#names + 1] = server_name
end
-- Get the logged in user from nbstat -- Get the server's name from nbstat
local result, user_name = netbios.get_user_name(host.ip) local result, server_name = netbios.get_server_name(host.ip)
if(result == true) then if(result == true) then
names[#names + 1] = user_name names[#names + 1] = server_name
end end
-- Look up the names, if any are valid than the server's SID will be returned -- Get the logged in user from nbstat
status, lookupnames2_result = msrpc.lsa_lookupnames2(socket, openpolicy2_result['policy_handle'], names, uid, tid, fid) local result, user_name = netbios.get_user_name(host.ip)
if(status == false) then if(result == true) then
msrpc.stop_smb(socket, uid, tid) names[#names + 1] = user_name
return false, lookupnames2_result end
end
-- Loop through the domains returned and find teh users in each -- Look up the names, if any are valid than the server's SID will be returned
for i = 1, #lookupnames2_result['domains'], 1 do status, lookupnames2_result = msrpc.lsa_lookupnames2(smbstate, openpolicy2_result['policy_handle'], names)
local domain = lookupnames2_result['domains'][i]['name'] if(status == false) then
local sid = lookupnames2_result['domains'][i]['sid'] msrpc.stop_smb(smbstate)
local rids = { } return false, lookupnames2_result
local start = 1000 end
-- Start by looking up 500 - 505 (will likely be Administrator + guest) -- Loop through the domains returned and find the users in each
for j = 500, 505, 1 do for i = 1, #lookupnames2_result['domains'], 1 do
local domain = lookupnames2_result['domains'][i]['name']
local sid = lookupnames2_result['domains'][i]['sid']
local rids = { }
local start = 1000
-- Start by looking up 500 - 505 (will likely be Administrator + guest)
for j = 500, 505, 1 do
rids[#rids + 1] = j rids[#rids + 1] = j
end end
status, lookupsids2_result = msrpc.lsa_lookupsids2(socket, openpolicy2_result['policy_handle'], sid, rids, uid, tid, fid) status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sid, rids)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
return false, lookupsids2_result else
end -- Put the details for each name into an array
for j = 1, #lookupsids2_result['details'], 1 do
-- Put the details for each name into an array if(lookupsids2_result['details'][j]['type'] ~= 8) then -- 8 = user not found
for j = 1, #lookupsids2_result['details'], 1 do local result = {}
if(lookupsids2_result['details'][j]['name'] ~= nil) then result['name'] = lookupsids2_result['details'][j]['name']
local result = {} result['rid'] = 500 + j - 1
result['name'] = lookupsids2_result['details'][j]['name'] result['domain'] = domain
result['rid'] = 500 + j - 1 result['typestr'] = lookupsids2_result['details'][j]['typestr']
result['domain'] = domain result['source'] = "LSA Bruteforce"
response[#response + 1] = result response[#response + 1] = result
end
end end
end end
-- Now do groups of 5 users, until we get past 1100 and have an empty group -- Now do groups of 5 users, until we get past 1100 and have an empty group
repeat repeat
rids = {} local used_names = 0
for j = start, start + 4, 1 do local rids = {}
for j = start, start + 4, 1 do
rids[#rids + 1] = j rids[#rids + 1] = j
end end
-- Try converting this group of RIDs into names -- Try converting this group of RIDs into names
status, lookupsids2_result = msrpc.lsa_lookupsids2(socket, openpolicy2_result['policy_handle'], sid, rids, uid, tid, fid) status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sid, rids)
if(status == false) then if(status == false) then
msrpc.stop_smb(socket, uid, tid) stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
return false, lookupsids2_result else
end -- Put the details for each name into an array
for j = 1, #lookupsids2_result['details'], 1 do
if(lookupsids2_result['details'][j]['type'] ~= 8) then -- 8 = user not found
local result = {}
result['name'] = lookupsids2_result['details'][j]['name']
result['rid'] = start + j - 1
result['domain'] = domain
result['typestr'] = lookupsids2_result['details'][j]['typestr']
result['source'] = "LSA Bruteforce"
response[#response + 1] = result
-- Put the details for each name into an array -- Increment the number of used names we have
for j = 1, #lookupsids2_result['details'], 1 do used_names = used_names + 1
if(lookupsids2_result['details'][j]['name'] ~= nil) then end
local result = {}
result['name'] = lookupsids2_result['details'][j]['name']
result['rid'] = start + j - 1
result['domain'] = domain
response[#response + 1] = result
end end
end end
-- Go to the next set of RIDs -- Go to the next set of RIDs
start = start + 5 start = start + 5
until #lookupsids2_result['names'] == 0 and start > 1100 until status == false or (used_names == 0 and start > 1100)
end
end -- Close the handle
msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle'])
-- Close the handle msrpc.stop_smb(smbstate)
msrpc.lsa_close(socket, openpolicy2_result['policy_handle'], uid, tid, fid)
msrpc.stop_smb(socket, uid, tid) stdnse.print_debug(3, "Leaving enum_lsa()")
return true, response return true, response
end end
@@ -323,41 +416,53 @@ end
action = function(host) action = function(host)
local i, j local i, j
local status local samr_status, lsa_status
local samr_result, lsa_result local samr_result, lsa_result
local names = {} local names = {}
local name_strings = {} local name_strings = {}
local response = " \n" local response = " \n"
-- Try enumerating through SAMR -- Try enumerating through LSA first. Since LSA provides less information, we want the
status, samr_result = enum_samr(host) -- SAMR result to overwrite it.
if(status == false) then lsa_status, lsa_result = enum_lsa(host)
response = response .. "Enum via SAMR error: " .. samr_result .. "\n" if(lsa_status == false) then
if(nmap.debugging() > 0) then
response = response .. "ERROR: couldn't enum through LSA: " .. lsa_result .. "\n"
end
else else
-- Copy the returned array into the names[] table, using the name as the key -- Copy the returned array into the names[] table, using the name as the key
stdnse.print_debug("EnumUsers: Received %d names from SAMR", #samr_result) stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
for i = 1, #lsa_result, 1 do
names[string.upper(lsa_result[i]['name'])] = lsa_result[i]
end
end
-- Try enumerating through SAMR
samr_status, samr_result = enum_samr(host)
if(samr_status == false) then
if(nmap.debugging() > 0) then
response = response .. "ERROR: couldn't enumerate through SAMR: " .. samr_result .. "\n"
end
else
-- Copy the returned array into the names[] table, using the name as the key
stdnse.print_debug(2, "EnumUsers: Received %d names from SAMR", #samr_result)
for i = 1, #samr_result, 1 do for i = 1, #samr_result, 1 do
names[string.upper(samr_result[i]['name'])] = samr_result[i] names[string.upper(samr_result[i]['name'])] = samr_result[i]
end end
end end
-- Try enumerating through LSA -- Check if both failed
status, lsa_result = enum_lsa(host) if(samr_status == false and lsa_status == false) then
if(status == false) then if(nmap.debugging() > 0) then
response = response .. "Enum via LSA error: " .. lsa_result .. "\n" return response
else else
-- Copy the returned array into the names[] table, using the name as the key return nil
stdnse.print_debug("EnumUsers: Received %d names from LSA", #samr_result)
for i = 1, #lsa_result, 1 do
if(names[lsa_result[i]['name']] == nil) then
names[string.upper(lsa_result[i]['name'])] = lsa_result[i]
end
end end
end end
-- Put the names into an array of strings, so we can sort them -- Put the names into an array of strings, so we can sort them
for name, details in pairs(names) do for name, details in pairs(names) do
name_strings[#name_strings + 1] = name name_strings[#name_strings + 1] = names[name]['name']
end end
-- Sort them -- Sort them
table.sort(name_strings, function (a, b) return string.lower(a) < string.lower(b) end) table.sort(name_strings, function (a, b) return string.lower(a) < string.lower(b) end)
@@ -368,16 +473,30 @@ action = function(host)
else else
-- If we're not verbose, just print out the names. Otherwise, print out everything we can -- If we're not verbose, just print out the names. Otherwise, print out everything we can
if(nmap.verbosity() < 1) then if(nmap.verbosity() < 1) then
response = response .. stdnse.strjoin(", ", name_strings) local response_array = {}
for i = 1, #name_strings, 1 do
local name = string.upper(name_strings[i])
response_array[#response_array + 1] = (names[name]['domain'] .. "\\" .. names[name]['name'])
end
response = response .. stdnse.strjoin(", ", response_array)
else else
for i = 1, #name_strings, 1 do for i = 1, #name_strings, 1 do
local name = name_strings[i] local name = string.upper(name_strings[i])
response = response .. string.format("%s\n", names[name]['name']) response = response .. string.format("%s\n", names[name]['name'])
if(names[name]['typestr'] ~= nil) then response = response .. string.format(" |_ Type: %s\n", names[name]['typestr']) end
if(names[name]['domain'] ~= nil) then response = response .. string.format(" |_ Domain: %s\n", names[name]['domain']) end if(names[name]['domain'] ~= nil) then response = response .. string.format(" |_ Domain: %s\n", names[name]['domain']) end
if(names[name]['rid'] ~= nil) then response = response .. string.format(" |_ RID: %s\n", names[name]['rid']) end if(nmap.verbosity() > 1) then
if(names[name]['rid'] ~= nil) then response = response .. string.format(" |_ RID: %s\n", names[name]['rid']) end
end
if(names[name]['fullname'] ~= nil) then response = response .. string.format(" |_ Full name: %s\n", names[name]['fullname']) end if(names[name]['fullname'] ~= nil) then response = response .. string.format(" |_ Full name: %s\n", names[name]['fullname']) end
if(names[name]['description'] ~= nil) then response = response .. string.format(" |_ Description: %s\n", names[name]['description']) end if(names[name]['description'] ~= nil) then response = response .. string.format(" |_ Description: %s\n", names[name]['description']) end
if(names[name]['flags'] ~= nil) then response = response .. string.format(" |_ Flags: %s\n", stdnse.strjoin(", ", names[name]['flags_list'])) end if(names[name]['flags'] ~= nil) then response = response .. string.format(" |_ Flags: %s\n", stdnse.strjoin(", ", names[name]['flags_list'])) end
if(nmap.verbosity() > 1) then
if(names[name]['source'] ~= nil) then response = response .. string.format(" |_ Source: %s\n", names[name]['source']) end
end
end end
end end
end end
@@ -385,4 +504,16 @@ action = function(host)
return response return response
end end
--real_action = action
--
-- function action (...)
-- local t = {n = select("#", ...), ...};
-- local status, ret = xpcall(function() return real_action(unpack(t, 1, t.n)) end, debug.traceback)
--
-- if not status then
-- error(ret)
-- end
--
-- return ret
-- end

View File

@@ -1,21 +1,52 @@
id = "OS from SMB" id = "OS from SMB"
description = [[ description = [[
Attempts to determine the operating system over the SMB protocol (ports 445 and Attempts to determine the operating system over the SMB protocol (ports 445 and
139). 139). Although the standard smb arguments can be used (for username/password), and
are respected by this script, they likely won't change the outcome in any meaningful
way.
See nselib/smb.lua for more information on this protocol. See nselib/smb.lua for more information on this protocol.
]] ]]
--- ---
--@usage --@usage
-- nmap --script smb-os-discovery.nse -p445 127.0.0.1\n -- nmap --script smb-os-discovery.nse -p445 127.0.0.1
-- sudo nmap -sU -sS --script smb-os-discovery.nse -p U:137,T:139 127.0.0.1 -- sudo nmap -sU -sS --script smb-os-discovery.nse -p U:137,T:139 127.0.0.1
-- --
--@output --@output
-- | OS from SMB: Windows 2000\n -- | OS from SMB: Windows 2000
-- | LAN Manager: Windows 2000 LAN Manager\n -- | LAN Manager: Windows 2000 LAN Manager
-- | Name: WORKGROUP\TEST1\n -- | Name: WORKGROUP\TEST1
-- |_ System time: 2008-09-09 20:55:55 UTC-5\n -- |_ System time: 2008-09-09 20:55:55 UTC-5
--
--@args smbusername The SMB username to log in with. The form DOMAIN\username and username@DOMAIN
-- are NOT understood. To set a domain, use the smbdomain argument.
--@args smbdomain The domain to log in with. If you aren't in a domained environment, then anything
-- will (should?) be accepted by the server.
--@args smbpassword The password to connect with. Be cautious with this, since some servers will lock
-- accounts if the incorrect password is given (although it's rare for the
-- 'administrator' account to be lockoutable, in the off chance that it is, you could
-- get yourself in trouble).
--@args smbhash A password hash to use when logging in. This is given as a single hex string (32
-- characters) or a pair of hex strings (2 x 32 characters, optionally separated by a
-- single character). These hashes are the Lanman or NTLM hash of the user's password,
-- and are stored by systems, on the harddrive or memory. They can be retrived from memory
-- using the fgdump or pwdump tools.
--@args smbguest If this is set to 'true' or '1', a 'guest' login will be attempted if the normal one
-- fails. This should be harmless, but I thought I would disable it by default anyway
-- because I'm not entirely sure of any possible consequences.
--@args smbtype The type of SMB authentication to use. By default, NTLMv1 is used, which is a pretty
-- decent compromise between security and compatibility. If you are paranoid, you might
-- want to use 'v2' or 'lmv2' for this (actually, if you're paranoid, you should be
-- avoiding this protocol altogether :P). If you're using an extremely old system, you
-- might need to set this to 'v1' or 'lm', which are less secure but more compatible.
--
-- If you want finer grained control, these are the possible options:
-- * v1 -- Sends LMv1 and NTLMv1
-- * LMv1 -- Sends LMv1 only
-- * NTLMv1 -- Sends NTLMv1 only (default)
-- * v2 -- Sends LMv2 and NTLMv2
-- * LMv2 -- Sends LMv2 only
-- --
----------------------------------------------------------------------- -----------------------------------------------------------------------
@@ -56,31 +87,48 @@ end
action = function(host) action = function(host)
local state
local status, err
-- Start up SMB -- Start up SMB
status, socket = smb.start(host) status, state = smb.start(host)
if(status == false) then if(status == false) then
return "Error: " .. socket if(nmap.debugging() > 0) then
return "ERROR: " .. state
else
return nil
end
end end
-- Negotiate protocol -- Negotiate protocol
status, negotiate_result = smb.negotiate_protocol(socket) status, err = smb.negotiate_protocol(state)
if(status == false) then if(status == false) then
stdnse.print_debug(2, "Negotiate session failed") stdnse.print_debug(2, "Negotiate session failed")
smb.stop(socket) smb.stop(state)
return "Error: " .. negotiate_result if(nmap.debugging() > 0) then
return "ERROR: " .. err
else
return nil
end
end end
-- Start a session -- Start a session
status, session_result = smb.start_session(socket, "", negotiate_result['session_key'], negotiate_result['capabilities']) status, err = smb.start_session(state, "")
if(status == false) then if(status == false) then
smb.stop(socket) smb.stop(state)
return "Error: " .. session_result if(nmap.debugging() > 0) then
return "ERROR: " .. err
else
return nil
end
end end
-- Kill SMB -- Kill SMB
smb.stop(socket, session_result['uid']) smb.stop(state)
return string.format("%s\nLAN Manager: %s\nName: %s\\%s\nSystem time: %s %s\n", get_windows_version(session_result['os']), session_result['lanmanager'], negotiate_result['domain'], negotiate_result['server'], negotiate_result['date'], negotiate_result['timezone_str']) return string.format("%s\nLAN Manager: %s\nName: %s\\%s\nSystem time: %s %s\n", get_windows_version(state['os']), state['lanmanager'], state['domain'], state['server'], state['date'], state['timezone_str'])
end end

View File

@@ -3,25 +3,29 @@ description = [[
Returns information about the SMB security level determined by SMB. Returns information about the SMB security level determined by SMB.
Here is how to interpret the output: Here is how to interpret the output:
\n\n
User-level security: Each user has a separate username/password that is used User-level security: Each user has a separate username/password that is used
to log into the system. This is the default setup of pretty much everything to log into the system. This is the default setup of pretty much everything
these days.\n these days.
Share-level security: The anonymous account should be used to log in, then Share-level security: The anonymous account should be used to log in, then
the password is given (in plaintext) when a share is accessed. All users who the password is given (in plaintext) when a share is accessed. All users who
have access to the share use this password. This was the original way of doing have access to the share use this password. This was the original way of doing
things, but isn't commonly seen, now. If a server uses share-level security, things, but isn't commonly seen, now. If a server uses share-level security,
it is vulnerable to sniffing. it is vulnerable to sniffing.
\n\n
Challenge/response passwords: If enabled, the server can accept any type of Challenge/response passwords: If enabled, the server can accept any type of
password:\n password:
* Plaintext * Plaintext
* LM and NTLM * LM and NTLM
* LMv2 and NTLMv2 * LMv2 and NTLMv2
If it isn't set, the server can only accept plaintext passwords. Most servers If it isn't set, the server can only accept plaintext passwords. Most servers
are configured to use challenge/response these days. If a server is configured are configured to use challenge/response these days. If a server is configured
to accept plaintext passwords, it is vulnerable to sniffing. to accept plaintext passwords, it is vulnerable to sniffing. LM and NTLM are
\n\n fairly secure, although there are some bruteforce attacks against them.
Message signing: If required, all messages between the client and server must Message signing: If required, all messages between the client and server must
sign be signed by a shared key, derived from the password and the server sign be signed by a shared key, derived from the password and the server
challenge. If supported and not required, message signing is negotiated between challenge. If supported and not required, message signing is negotiated between
@@ -31,19 +35,50 @@ the server, messages probably won't be signed; additionally, if performing a
man-in-the-middle attack, an attacker can negotiate no message signing. If man-in-the-middle attack, an attacker can negotiate no message signing. If
message signing isn't required, the server is vulnerable to man-in-the-middle message signing isn't required, the server is vulnerable to man-in-the-middle
attacks. attacks.
\n\n
See nselib/smb.lua for more information on the protocol itself.\n
]]
See nselib/smb.lua for more information on the protocol itself.
This script will allow you to use smb arguments (username/password), but it probably
won't ever require them.
]]
--- ---
--@usage --@usage
-- nmap --script smb-security-mode.nse -p445 127.0.0.1\n -- nmap --script smb-security-mode.nse -p445 127.0.0.1
-- sudo nmap -sU -sS --script smb-security-mode.nse -p U:137,T:139 127.0.0.1\n -- sudo nmap -sU -sS --script smb-security-mode.nse -p U:137,T:139 127.0.0.1
-- --
--@output --@output
-- | SMB Security: User-level authentication\n -- | SMB Security: User-level authentication
-- | SMB Security: Challenge/response passwords supported\n -- | SMB Security: Challenge/response passwords supported
-- |_ SMB Security: Message signing supported\n -- |_ SMB Security: Message signing supported
--
--@args smbusername The SMB username to log in with. The form DOMAIN\username and username@DOMAIN
-- are NOT understood. To set a domain, use the smbdomain argument.
--@args smbdomain The domain to log in with. If you aren't in a domained environment, then anything
-- will (should?) be accepted by the server.
--@args smbpassword The password to connect with. Be cautious with this, since some servers will lock
-- accounts if the incorrect password is given (although it's rare for the
-- 'administrator' account to be lockoutable, in the off chance that it is, you could
-- get yourself in trouble).
--@args smbhash A password hash to use when logging in. This is given as a single hex string (32
-- characters) or a pair of hex strings (2 x 32 characters, optionally separated by a
-- single character). These hashes are the Lanman or NTLM hash of the user's password,
-- and are stored by systems, on the harddrive or memory. They can be retrived from memory
-- using the fgdump or pwdump tools.
--@args smbguest If this is set to 'true' or '1', a 'guest' login will be attempted if the normal one
-- fails. This should be harmless, but I thought I would disable it by default anyway
-- because I'm not entirely sure of any possible consequences.
--@args smbtype The type of SMB authentication to use. By default, NTLMv1 is used, which is a pretty
-- decent compromise between security and compatibility. If you are paranoid, you might
-- want to use 'v2' or 'lmv2' for this (actually, if you're paranoid, you should be
-- avoiding this protocol altogether :P). If you're using an extremely old system, you
-- might need to set this to 'v1' or 'lm', which are less secure but more compatible.
--
-- If you want finer grained control, these are the possible options:
-- * v1 -- Sends LMv1 and NTLMv1
-- * LMv1 -- Sends LMv1 only
-- * NTLMv1 -- Sends NTLMv1 only (default)
-- * v2 -- Sends LMv2 and NTLMv2
-- * LMv2 -- Sends LMv2 only
-- --
----------------------------------------------------------------------- -----------------------------------------------------------------------
@@ -69,20 +104,31 @@ end
action = function(host) action = function(host)
local status, socket = smb.start(host) local state
local status, err
status, state = smb.start(host)
if(status == false) then if(status == false) then
return "Error: " .. socket if(nmap.debugging() > 0) then
return "ERROR: " .. state
else
return nil
end
end end
status, result = smb.negotiate_protocol(socket) status, err = smb.negotiate_protocol(state)
if(status == false) then if(status == false) then
smb.stop(socket) smb.stop(state)
return "Error: " .. result if(nmap.debugging() > 0) then
return "ERROR: " .. err
else
return nil
end
end end
local security_mode = result['security_mode'] local security_mode = state['security_mode']
local response = "" local response = ""
-- User-level authentication or share-level authentication -- User-level authentication or share-level authentication
@@ -108,7 +154,7 @@ action = function(host)
response = response .. "SMB Security: Message signing not supported\n" response = response .. "SMB Security: Message signing not supported\n"
end end
smb.stop(socket) smb.stop(state)
return response return response
end end

145
scripts/smb-serverstats.nse Normal file
View File

@@ -0,0 +1,145 @@
id = "MSRPC: Server statistics"
description = [[
Attempts to grab the server's statistics over SMB + MSRPC, which uses TCP
ports 445 or 139.
An administrative account is required to pull these statistics on most versions
of Windows, and Vista doesn't seem to let even the administrator account pull them.
Some of the numbers returned here don't feel right to me, but they're definitely
the numbers that Windows returns. Take the values here with a grain of salt.
]]
---
-- @usage
-- nmap --script smb-serverstats.nse -p445 <host>
-- sudo nmap -sU -sS --script smb-serverstats.nse -p U:137,T:139 <host>
--
-- @output
-- Host script results:
-- | MSRPC: Server statistics:
-- | Server statistics collected since 2008-10-17 09:32:41 (4d0h24m29s):
-- | |_ Traffic 133467 bytes (0.38b/s) sent, 167696 bytes (0.48b/s) received
-- | |_ Failed logins: 5
-- | |_ Permission errors: 1, System errors: 0
-- | |_ Print jobs spooled: 0
-- |_ |_ Files opened (including pipes): 18
--
--@args smbusername The SMB username to log in with. The form DOMAIN\username and username@DOMAIN
-- are NOT understood. To set a domain, use the smbdomain argument.
--@args smbdomain The domain to log in with. If you aren't in a domained environment, then anything
-- will (should?) be accepted by the server.
--@args smbpassword The password to connect with. Be cautious with this, since some servers will lock
-- accounts if the incorrect password is given (although it's rare for the
-- 'administrator' account to be lockoutable, in the off chance that it is, you could
-- get yourself in trouble).
--@args smbhash A password hash to use when logging in. This is given as a single hex string (32
-- characters) or a pair of hex strings (2 x 32 characters, optionally separated by a
-- single character). These hashes are the Lanman or NTLM hash of the user's password,
-- and are stored by systems, on the harddrive or memory. They can be retrived from memory
-- using the fgdump or pwdump tools.
--@args smbguest If this is set to 'true' or '1', a 'guest' login will be attempted if the normal one
-- fails. This should be harmless, but I thought I would disable it by default anyway
-- because I'm not entirely sure of any possible consequences.
--@args smbtype The type of SMB authentication to use. By default, NTLMv1 is used, which is a pretty
-- decent compromise between security and compatibility. If you are paranoid, you might
-- want to use 'v2' or 'lmv2' for this (actually, if you're paranoid, you should be
-- avoiding this protocol altogether :P). If you're using an extremely old system, you
-- might need to set this to 'v1' or 'lm', which are less secure but more compatible.
--
-- If you want finer grained control, these are the possible options:
-- * v1 -- Sends LMv1 and NTLMv1
-- * LMv1 -- Sends LMv1 only
-- * NTLMv1 -- Sends NTLMv1 only (default)
-- * v2 -- Sends LMv2 and NTLMv2
-- * LMv2 -- Sends LMv2 only
--
-----------------------------------------------------------------------
author = "Ron Bowes"
copyright = "Ron Bowes"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery","intrusive"}
require 'msrpc'
require 'smb'
require 'stdnse'
hostrule = function(host)
local port = smb.get_port(host)
if(port == nil) then
return false
else
return true
end
end
action = function(host)
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
if(status == false) then
if(nmap.debugging() > 0) then
return "ERROR: " .. smbstate
else
return nil
end
end
-- Bind to SRVSVC service
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then
smb.stop(smbstate)
if(nmap.debugging() > 0) then
return "ERROR: " .. bind_result
else
return nil
end
end
-- Call netservergetstatistics for 'server'
status, netservergetstatistics_result = msrpc.srvsvc_netservergetstatistics(smbstate, host.ip)
if(status == false) then
smb.stop(smbstate)
if(nmap.debugging() > 0) then
return "ERROR: " .. netservergetstatistics_result
else
return nil
end
end
-- Stop the session
smb.stop(smbstate)
-- Build the response
local response = " \n"
local period = os.time() - netservergetstatistics_result['start']
local period_str
if(period == 0) then
period = 1
end
if(period > 60 * 60 * 24) then
period_str = string.format("%dd%dh%02dm%02ds", period / (60*60*24), (period % (60*60*24)) / 3600, (period % 3600) / 60, period % 60)
elseif(period > 60 * 60) then
period_str = string.format("%dh%02dm%02ds", period / 3600, (period % 3600) / 60, period % 60)
else
period_str = string.format("%02dm%02ds", period / 60, period % 60)
end
stats = netservergetstatistics_result
response = response .. string.format("Server statistics collected since %s (%s):\n", netservergetstatistics_result['start_date'], period_str)
response = response .. string.format("|_ Traffic %d bytes (%.2fb/s) sent, %d bytes (%.2fb/s) received\n", stats['bytessent'], stats['bytessent'] / period, stats['bytesrcvd'], stats['bytesrcvd'] / period)
response = response .. string.format("|_ Failed logins: %d\n", stats['pwerrors'])
response = response .. string.format("|_ Permission errors: %d, System errors: %d\n", stats['permerrors'], stats['syserrors'])
response = response .. string.format("|_ Print jobs spooled: %s\n", stats['jobsqueued'])
response = response .. string.format("|_ Files opened (including pipes): %d\n", stats['fopens'])
return response
end

265
scripts/smb-systeminfo.nse Normal file
View File

@@ -0,0 +1,265 @@
id = "System info"
description = [[
Pulls back information about the remote system from the registry. Getting all
of the information requires an administrative account, although a user account
will still get a lot of it. Guest probably won't get any, nor will anonymous.
This goes for all operating systems, including Windows 2000.
Windows Vista doesn't appear to have the WINREG binding (or it's different and
I don't know it), so this doesn't support Vista at all.
]]
---
-- @usage
-- nmap --script smb-systeminfo.nse -p445 <host>
-- sudo nmap -sU -sS --script smb-systeminfo.nse -p U:137,T:139 <host>
--
-- @output
-- Host script results:
-- | System info:
-- | OS Details
-- | |_ Microsoft Windows Server 2003 Service Pack 2 (ServerNT 5.2 build 3790)
-- | |_ Installed on 2007-11-26 23:40:40
-- | |_ Registered to IPC (organization: MYCOMPANY)
-- | |_ Path: %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;C:\Program Files\Microsoft SQL Server\90\Tools\binn\;C:\Program Files\IBM\Rational AppScan\
-- | |_ Systemroot: C:\WINDOWS
-- | |_ Page files: C:\pagefile.sys 2046 4092 (cleared at shutdown => 0)
-- | Hardware
-- | |_ CPU 0: Intel(R) Xeon(TM) CPU 2.80GHz [2780mhz GenuineIntel]
-- | |_ Identifier 0: x86 Family 15 Model 2 Stepping 9
-- | |_ CPU 1: Intel(R) Xeon(TM) CPU 2.80GHz [2780mhz GenuineIntel]
-- | |_ Identifier 1: x86 Family 15 Model 2 Stepping 9
-- | |_ CPU 2: Intel(R) Xeon(TM) CPU 2.80GHz [2780mhz GenuineIntel]
-- | |_ Identifier 2: x86 Family 15 Model 2 Stepping 9
-- | |_ CPU 3: Intel(R) Xeon(TM) CPU 2.80GHz [2780mhz GenuineIntel]
-- | |_ Identifier 3: x86 Family 15 Model 2 Stepping 9
-- | |_ Video driver: RAGE XL PCI Family (Microsoft Corporation)
-- | Browsers
-- | |_ Internet Explorer 7.0000
-- |_ |_ Firefox 3.0.3 (en-US)
--
--@args smbusername The SMB username to log in with. The form DOMAIN\username and username@DOMAIN
-- are NOT understood. To set a domain, use the smbdomain argument.
--@args smbdomain The domain to log in with. If you aren't in a domained environment, then anything
-- will (should?) be accepted by the server.
--@args smbpassword The password to connect with. Be cautious with this, since some servers will lock
-- accounts if the incorrect password is given (although it's rare for the
-- 'administrator' account to be lockoutable, in the off chance that it is, you could
-- get yourself in trouble).
--@args smbhash A password hash to use when logging in. This is given as a single hex string (32
-- characters) or a pair of hex strings (2 x 32 characters, optionally separated by a
-- single character). These hashes are the Lanman or NTLM hash of the user's password,
-- and are stored by systems, on the harddrive or memory. They can be retrived from memory
-- using the fgdump or pwdump tools.
--@args smbguest If this is set to 'true' or '1', a 'guest' login will be attempted if the normal one
-- fails. This should be harmless, but I thought I would disable it by default anyway
-- because I'm not entirely sure of any possible consequences.
--@args smbtype The type of SMB authentication to use. By default, NTLMv1 is used, which is a pretty
-- decent compromise between security and compatibility. If you are paranoid, you might
-- want to use 'v2' or 'lmv2' for this (actually, if you're paranoid, you should be
-- avoiding this protocol altogether :P). If you're using an extremely old system, you
-- might need to set this to 'v1' or 'lm', which are less secure but more compatible.
--
-- If you want finer grained control, these are the possible options:
-- * v1 -- Sends LMv1 and NTLMv1
-- * LMv1 -- Sends LMv1 only
-- * NTLMv1 -- Sends NTLMv1 only (default)
-- * v2 -- Sends LMv2 and NTLMv2
-- * LMv2 -- Sends LMv2 only
--
-----------------------------------------------------------------------
author = "Ron Bowes"
copyright = "Ron Bowes"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery","intrusive"}
require 'msrpc'
require 'smb'
require 'stdnse'
hostrule = function(host)
local port = smb.get_port(host)
if(port == nil) then
return false
else
return true
end
end
---Retrieves the requested value from the registry.
--@param smbstate The SMB table we're using, bound to the WINREG service.
--@param handle The handle to the hive (HKLM or HKU, for example)
--@param key The full path of the key to retrieve (like "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment")
--@param value The value to retrieve (like, "NUMBER_OF_PROCESSORS")
--@return (status, result) If status is false, result is an error message. Otherwise, result is the value of the key.
local function reg_get_value(smbstate, handle, key, value)
-- Open the key
status, openkey_result = msrpc.winreg_openkey(smbstate, handle, key)
if(status == false) then
return false, openkey_result
end
-- Query the value
status, queryvalue_result = msrpc.winreg_queryvalue(smbstate, openkey_result['handle'], value)
if(status == false) then
return false, queryvalue_result
end
-- Close the key
status, closekey_result = msrpc.winreg_closekey(smbstate, openkey_result['handle'], value)
if(status == false) then
return false, closekey_result
end
return true, queryvalue_result['value']
end
local function get_info_registry(host)
local result = {}
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to WINREG service
status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Open HKEY_LOCAL_MACHINE
status, openhklm_result = msrpc.winreg_openhklm(smbstate)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, openhklm_result
end
-- Processor information
result['status-number_of_processors'], result['number_of_processors'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "NUMBER_OF_PROCESSORS")
if(status == false) then
result['number_of_processors'] = 0
end
result['status-os'], result['os'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "OS")
result['status-path'], result['path'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "Path")
result['status-processor_architecture'], result['processor_architecture'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "PROCESSOR_ARCHITECTURE")
result['status-processor_identifier'], result['processor_identifier'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "PROCESSOR_IDENTIFIER")
result['status-processor_level'], result['processor_level'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "PROCESSOR_LEVEL")
result['status-processor_revision'], result['processor_revision'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "PROCESSOR_REVISION")
for i = 0, result['number_of_processors'] - 1, 1 do
result['status-~mhz'..i], result['~mhz' .. i] = reg_get_value(smbstate, openhklm_result['handle'], "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\" .. i, "~MHz")
result['status-identifier'..i], result['identifier' .. i] = reg_get_value(smbstate, openhklm_result['handle'], "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\" .. i, "Identifier")
result['status-processornamestring'..i], result['processornamestring' .. i] = reg_get_value(smbstate, openhklm_result['handle'], "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\" .. i, "ProcessorNameString")
result['status-vendoridentifier'..i], result['vendoridentifier' .. i] = reg_get_value(smbstate, openhklm_result['handle'], "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\" .. i, "VendorIdentifier")
end
-- status, result['physicalmemory'] = reg_get_value(smbstate, openhklm_result['handle'], "HARDWARE\\ResourceMap\\System Resources\\Physical Memory", ".Translated")
-- TODO: Known DLLs?
-- Paging file
result['status-pagingfiles'], result['pagingfiles'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management", "PagingFiles")
result['status-clearpagefileatshutdown'], result['clearpagefileatshutdown'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management", "ClearPageFileAtShutdown")
-- OS Information
result['status-csdversion'], result['csdversion'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Windows NT\\CurrentVersion", "CSDVersion")
if(result['status-csdversion'] == false) then
result['csdversion'] = "(no service packs)"
end
result['status-currentbuildnumber'], result['currentbuildnumber'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentBuildNumber")
result['status-currenttype'], result['currenttype'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentType")
result['status-currentversion'], result['currentversion'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Windows NT\\CurrentVersion", "CurrentVersion")
result['status-installdate'], result['installdate'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Windows NT\\CurrentVersion", "InstallDate")
if(result['status-installdate'] ~= false) then
result['installdate'] = os.date("%Y-%m-%d %H:%M:%S", result['installdate'])
end
result['status-productname'], result['productname'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Windows NT\\CurrentVersion", "Productname")
result['status-registeredowner'], result['registeredowner'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Windows NT\\CurrentVersion", "RegisteredOwner")
result['status-registeredorganization'], result['registeredorganization'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Windows NT\\CurrentVersion", "RegisteredOrganization")
result['status-systemroot'], result['systemroot'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Windows NT\\CurrentVersion", "SystemRoot")
result['status-producttype'], result['producttype'] = reg_get_value(smbstate, openhklm_result['handle'], "System\\CurrentControlSet\\Control\\ProductOptions", "ProductType")
result['status-productsuite'], result['productsuite'] = reg_get_value(smbstate, openhklm_result['handle'], "System\\CurrentControlSet\\Control\\ProductOptions", "ProductSuite")
-- Driver information
result['status-video_driverdesc'], result['video_driverdesc'] = reg_get_value(smbstate, openhklm_result['handle'], "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E968-E325-11CE-BFC1-08002BE10318}\\0000", "DriverDesc")
-- Software versions
result['status-ie_version'], result['ie_version'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Microsoft\\Internet Explorer\\Version Vector", "IE")
result['status-ff_version'], result['ff_version'] = reg_get_value(smbstate, openhklm_result['handle'], "Software\\Mozilla\\Mozilla Firefox", "CurrentVersion")
if(result['status-ff_version'] == false) then
result['ff_version'] = "<not installed>"
end
msrpc.stop_smb(smbstate)
return true, result
end
action = function(host)
status, result = get_info_registry(host)
if(status == false) then
if(nmap.debugging() > 0) then
return "ERROR: " .. result
else
return nil
end
else
local response = " \n"
if(result['status-os'] == true) then
response = response .. string.format("OS Details\n")
response = response .. string.format("|_ %s %s (%s %s build %s)\n", result['productname'], result['csdversion'], result['producttype'], result['currentversion'], result['currentbuildnumber'])
response = response .. string.format("|_ Installed on %s\n", result['installdate'])
response = response .. string.format("|_ Registered to %s (organization: %s)\n", result['registeredowner'], result['registeredorganization'])
response = response .. string.format("|_ Path: %s\n", result['path'])
response = response .. string.format("|_ Systemroot: %s\n", result['systemroot'])
response = response .. string.format("|_ Page files: %s (cleared at shutdown => %s)\n", result['pagingfiles'], result['clearpagefileatshutdown'])
response = response .. string.format("Hardware\n")
for i = 0, result['number_of_processors'] - 1, 1 do
response = response .. string.format("|_ CPU %d: %s [%dmhz %s]\n", i, result['processornamestring'..i], result['~mhz'..i], result['vendoridentifier'..i])
response = response .. string.format("|_ Identifier %d: %s\n", i, result['identifier'..i])
end
response = response .. string.format("|_ Video driver: %s\n", result['video_driverdesc'])
response = response .. string.format("Browsers\n")
response = response .. string.format("|_ Internet Explorer %s\n", result['ie_version'])
if(result['status-ff_version']) then
response = response .. string.format("|_ Firefox %s\n", result['ff_version'])
end
elseif(result['status-productname'] == true) then
if(nmap.debugging() > 0) then
response = response .. string.format("|_ Access was denied for certain values; try an administrative account for more complete information\n")
end
response = response .. string.format("OS Details\n")
response = response .. string.format("|_ %s %s (%s %s build %s)\n", result['productname'], result['csdversion'], result['producttype'], result['currentversion'], result['currentbuildnumber'])
response = response .. string.format("|_ Installed on %s\n", result['installdate'])
response = response .. string.format("|_ Registered to %s (organization: %s)\n", result['registeredowner'], result['registeredorganization'])
response = response .. string.format("|_ Systemroot: %s\n", result['systemroot'])
else
if(nmap.debugging() > 0) then
response = string.format("|_ Account being used was unable to probe for information, try using an administrative account\n")
else
response = nil
end
end
return response
end
end