mirror of
https://github.com/nmap/nmap.git
synced 2025-12-22 07:29:01 +00:00
Merging changes from my experimental branch; the new versions of this scripts, which have significant changes to their core functionality, managed to hold their own against Brandon's network. More testing would be very helpful, though, especially with credentials (most of Brandon's scans were anonymous).
This commit is contained in:
@@ -9,10 +9,10 @@ can be fine tuned using Nmap parameters. For the most possible information,
|
||||
leave the defaults; however, there are advantages to using them individually.
|
||||
|
||||
Advantages of using SAMR enumeration:
|
||||
* Stealthier (requires one packet/user account, whereas LSA uses at least 20
|
||||
packets; additionally, LSA makes a lot of noise in the Windows event log (LSA
|
||||
enumeration is the only script I (Ron Bowes) have been called on by the
|
||||
administrator of a box I was testing against).
|
||||
* Stealthier (requires one packet/user account, whereas LSA uses at least 10
|
||||
packets while SAMR uses half that; additionally, LSA makes a lot of noise in
|
||||
the Windows event log (LSA enumeration is the only script I (Ron Bowes) have
|
||||
been called on by the administrator of a box I was testing against).
|
||||
* More information is returned (more than just the username).
|
||||
* Every account will be found, since they're being enumerated with a function
|
||||
that's designed to enumerate users.
|
||||
@@ -58,16 +58,11 @@ a user on a domain or system. An LSA function is exposed which lets us convert t
|
||||
(say, 1000) to the username (say, "Ron"). So, the technique will essentially try
|
||||
converting 1000 to a name, then 1001, 1002, etc., until we think we're done.
|
||||
|
||||
To do this, this script breaks users into groups of five RIDs, then checked individually
|
||||
(checking too many at once causes problems). We continue checking until we reach
|
||||
1100, and get an empty group of five. This probably isn't the most effective way, but it
|
||||
seems to work. It might be a good idea to modify this, in the future, with some more
|
||||
intelligence. I (Ron Bowes) performed a test on an old server with a lot of accounts,
|
||||
and these were the active RIDs: 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, in an automated check. An ideal solution might be to continue
|
||||
doing groups of 5, but wait until we get 5-10 consecutive empty groups before giving up.
|
||||
To do this, the script breaks users into groups of RIDs based on the <code>LSA_GROUPSIZE</code>
|
||||
constant. All members of this group are checked simultaneously, and the responses recorded.
|
||||
When a series of empty groups are found (<code>LSA_MINEMPTY</code> groups, specifically),
|
||||
the scan ends. As long as you are getting a few groups with active accounts, the scan will
|
||||
continue.
|
||||
|
||||
Before attempting this conversion, the SID of the server has to be determined.
|
||||
The SID is determined by doing the reverse operation; that is, by converting a name into
|
||||
@@ -156,284 +151,8 @@ hostrule = function(host)
|
||||
return smb.get_port(host) ~= nil
|
||||
end
|
||||
|
||||
---Attempt to enumerate users through SAMR methods. See the file description for more information.
|
||||
--
|
||||
--@param host The host object.
|
||||
--@return Status (true or false).
|
||||
--@return Array of user tables (if status is true) or an an error string (if
|
||||
--status is false). Each user table contains the fields <code>name</code>,
|
||||
--<code>domain</code>, <code>fullname</code>, <code>rid</code>, and
|
||||
--<code>description</code>.
|
||||
local function enum_samr(host)
|
||||
local i, j
|
||||
|
||||
stdnse.print_debug(3, "Entering enum_samr()")
|
||||
|
||||
local smbstate
|
||||
local bind_result, connect4_result, enumdomains_result
|
||||
local connect_handle
|
||||
local status, smbstate
|
||||
local response = {}
|
||||
|
||||
-- Create the SMB session
|
||||
status, smbstate = msrpc.start_smb(host, msrpc.SAMR_PATH)
|
||||
|
||||
if(status == false) then
|
||||
return false, smbstate
|
||||
end
|
||||
|
||||
-- Bind to SAMR service
|
||||
status, bind_result = msrpc.bind(smbstate, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, bind_result
|
||||
end
|
||||
|
||||
-- Call connect4()
|
||||
status, connect4_result = msrpc.samr_connect4(smbstate, host.ip)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, connect4_result
|
||||
end
|
||||
|
||||
-- Save the connect_handle
|
||||
connect_handle = connect4_result['connect_handle']
|
||||
|
||||
-- Call EnumDomains()
|
||||
status, enumdomains_result = msrpc.samr_enumdomains(smbstate, connect_handle)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, enumdomains_result
|
||||
end
|
||||
|
||||
-- If no domains were returned, go back with an error
|
||||
if(#enumdomains_result['sam']['entries'] == 0) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, "Couldn't find any domains"
|
||||
end
|
||||
|
||||
-- Now, loop through the domains and find the users
|
||||
for i = 1, #enumdomains_result['sam']['entries'], 1 do
|
||||
|
||||
local domain = enumdomains_result['sam']['entries'][i]['name']
|
||||
-- We don't care about the 'builtin' domain, in all my tests it's empty
|
||||
if(domain ~= 'Builtin') then
|
||||
local sid
|
||||
local domain_handle
|
||||
local opendomain_result, querydisplayinfo_result
|
||||
|
||||
-- Call LookupDomain()
|
||||
status, lookupdomain_result = msrpc.samr_lookupdomain(smbstate, connect_handle, domain)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, lookupdomain_result
|
||||
end
|
||||
|
||||
-- Save the sid
|
||||
sid = lookupdomain_result['sid']
|
||||
|
||||
-- Call OpenDomain()
|
||||
status, opendomain_result = msrpc.samr_opendomain(smbstate, connect_handle, sid)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, opendomain_result
|
||||
end
|
||||
|
||||
-- Save the domain handle
|
||||
domain_handle = opendomain_result['domain_handle']
|
||||
|
||||
-- Loop as long as we're getting valid results
|
||||
j = 0
|
||||
repeat
|
||||
-- Call QueryDisplayInfo()
|
||||
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle, j)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, querydisplayinfo_result
|
||||
end
|
||||
|
||||
-- Save the response
|
||||
if(querydisplayinfo_result['return'] ~= 0 and querydisplayinfo_result['info'] ~= nil and querydisplayinfo_result['info']['entries'] ~= nil and querydisplayinfo_result['info']['entries'][1] ~= nil) then
|
||||
local array = {}
|
||||
local k
|
||||
|
||||
-- The reason these are all indexed from '1' is because we request names one at a time.
|
||||
array['domain'] = domain
|
||||
array['name'] = querydisplayinfo_result['info']['entries'][1]['account_name']
|
||||
array['fullname'] = querydisplayinfo_result['info']['entries'][1]['full_name']
|
||||
array['description'] = querydisplayinfo_result['info']['entries'][1]['description']
|
||||
array['rid'] = querydisplayinfo_result['info']['entries'][1]['rid']
|
||||
array['flags'] = querydisplayinfo_result['info']['entries'][1]['acct_flags']
|
||||
array['source'] = "SAMR Enumeration"
|
||||
|
||||
-- Clean up the 'flags' array
|
||||
for k = 1, #array['flags'], 1 do
|
||||
array['flags'][k] = msrpc.samr_AcctFlags_tostr(array['flags'][k])
|
||||
end
|
||||
|
||||
-- Add it to the array
|
||||
response[#response + 1] = array
|
||||
end
|
||||
j = j + 1
|
||||
until querydisplayinfo_result['return'] == 0
|
||||
|
||||
-- Close the domain handle
|
||||
msrpc.samr_close(smbstate, domain_handle)
|
||||
|
||||
-- Finally, fill in the response!
|
||||
-- for i = 1, #querydisplayinfo_result['details'], 1 do
|
||||
-- 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]
|
||||
-- end
|
||||
end -- Checking for 'builtin'
|
||||
end -- Domain loop
|
||||
|
||||
-- Close the connect handle
|
||||
msrpc.samr_close(smbstate, connect_handle)
|
||||
|
||||
-- Stop the SAMR SMB
|
||||
msrpc.stop_smb(smbstate)
|
||||
|
||||
stdnse.print_debug(3, "Leaving enum_samr()")
|
||||
|
||||
return true, response
|
||||
end
|
||||
|
||||
---Attempt to enumerate users through LSA methods. See the file description for more information.
|
||||
--
|
||||
--@param host The host object.
|
||||
--@return Status (true or false).
|
||||
--@return Array of user tables (if status is true) or an an error string (if
|
||||
--status is false). Each user table contains the fields <code>name</code>,
|
||||
--<code>domain</code>, and <code>rid</code>.
|
||||
local function enum_lsa(host)
|
||||
|
||||
local smbstate
|
||||
local status
|
||||
local response = {}
|
||||
|
||||
stdnse.print_debug(3, "Entering enum_lsa()")
|
||||
|
||||
-- Create the 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
|
||||
|
||||
-- Open the LSA policy
|
||||
status, openpolicy2_result = msrpc.lsa_openpolicy2(smbstate, host.ip)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, openpolicy2_result
|
||||
end
|
||||
|
||||
-- Start with some common names, as well as the name returned by the negotiate call
|
||||
-- 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?)
|
||||
names = {"administrator", "guest", "test", smbstate['domain'], string.sub(smbstate['server'], 1, #smbstate['server'] - 1) }
|
||||
|
||||
-- Get the server's name from nbstat
|
||||
local result, server_name = netbios.get_server_name(host.ip)
|
||||
if(result == true) then
|
||||
names[#names + 1] = server_name
|
||||
end
|
||||
|
||||
-- Get the logged in user from nbstat
|
||||
local result, user_name = netbios.get_user_name(host.ip)
|
||||
if(result == true) then
|
||||
names[#names + 1] = user_name
|
||||
end
|
||||
|
||||
-- Look up the names, if any are valid than the server's SID will be returned
|
||||
status, lookupnames2_result = msrpc.lsa_lookupnames2(smbstate, openpolicy2_result['policy_handle'], names)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, lookupnames2_result
|
||||
end
|
||||
-- Loop through the domains returned and find the users in each
|
||||
for i = 1, #lookupnames2_result['domains']['domains'], 1 do
|
||||
local domain = lookupnames2_result['domains']['domains'][i]['name']
|
||||
local sid = lookupnames2_result['domains']['domains'][i]['sid']
|
||||
local sids = { }
|
||||
local start = 1000
|
||||
|
||||
-- Start by looking up 500 - 505 (will likely be Administrator + guest)
|
||||
for j = 500, 505, 1 do
|
||||
sids[#sids + 1] = sid .. "-" .. j
|
||||
end
|
||||
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids)
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
|
||||
else
|
||||
-- Put the details for each name into an array
|
||||
-- NOTE: Be sure to mirror any changes here in the next bit!
|
||||
for j = 1, #lookupsids2_result['names']['names'], 1 do
|
||||
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
|
||||
local result = {}
|
||||
result['name'] = lookupsids2_result['names']['names'][j]['name']
|
||||
result['rid'] = 500 + j - 1
|
||||
result['domain'] = domain
|
||||
result['typestr'] = msrpc.lsa_SidType_tostr(lookupsids2_result['names']['names'][j]['sid_type'])
|
||||
result['source'] = "LSA Bruteforce"
|
||||
response[#response + 1] = result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Now do groups of 5 users, until we get past 1100 and have an empty group
|
||||
repeat
|
||||
local used_names = 0
|
||||
local sids = {}
|
||||
for j = start, start + 4, 1 do
|
||||
sids[#sids + 1] = sid .. "-" .. j
|
||||
end
|
||||
|
||||
-- Try converting this group of RIDs into names
|
||||
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids)
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
|
||||
else
|
||||
-- Put the details for each name into an array
|
||||
for j = 1, #lookupsids2_result['names']['names'], 1 do
|
||||
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
|
||||
local result = {}
|
||||
result['name'] = lookupsids2_result['names']['names'][j]['name']
|
||||
result['rid'] = start + j - 1
|
||||
result['domain'] = domain
|
||||
result['typestr'] = msrpc.lsa_SidType_tostr(lookupsids2_result['names']['names'][j]['sid_type'])
|
||||
result['source'] = "LSA Bruteforce"
|
||||
response[#response + 1] = result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Go to the next set of RIDs
|
||||
start = start + 5
|
||||
until status == false or (used_names == 0 and start > 1100)
|
||||
end
|
||||
|
||||
-- Close the handle
|
||||
msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle'])
|
||||
|
||||
msrpc.stop_smb(smbstate)
|
||||
|
||||
stdnse.print_debug(3, "Leaving enum_lsa()")
|
||||
|
||||
return true, response
|
||||
end
|
||||
|
||||
|
||||
|
||||
action = function(host)
|
||||
|
||||
local i, j
|
||||
local samr_status = false
|
||||
local lsa_status = false
|
||||
@@ -450,7 +169,7 @@ action = function(host)
|
||||
-- Try enumerating through LSA first. Since LSA provides less information, we want the
|
||||
-- SAMR result to overwrite it.
|
||||
if(do_lsa) then
|
||||
lsa_status, lsa_result = enum_lsa(host)
|
||||
lsa_status, lsa_result = msrpc.lsa_enum_users(host)
|
||||
if(lsa_status == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
response = response .. "ERROR: Couldn't enumerate through LSA: " .. lsa_result .. "\n"
|
||||
@@ -468,7 +187,7 @@ action = function(host)
|
||||
|
||||
-- Try enumerating through SAMR
|
||||
if(do_samr) then
|
||||
samr_status, samr_result = enum_samr(host)
|
||||
samr_status, samr_result = msrpc.samr_enum_users(host)
|
||||
if(samr_status == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
response = response .. "ERROR: Couldn't enumerate through SAMR: " .. samr_result .. "\n"
|
||||
@@ -523,6 +242,7 @@ action = function(host)
|
||||
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]['flags'] ~= nil) then response = response .. string.format(" |_ Flags: %s\n", stdnse.strjoin(", ", names[name]['flags'])) end
|
||||
|
||||
if(nmap.verbosity() > 1) then
|
||||
|
||||
Reference in New Issue
Block a user