1
0
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:
ron
2008-12-24 00:53:01 +00:00
parent a246aaf469
commit 773000b65a
17 changed files with 1930 additions and 515 deletions

View File

@@ -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