mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 05:01:29 +00:00
Importing changes from my branch. There are two major updates:
1) I wrote a function that formats output from scripts in a consistent way. Although we haven't really come to a concensus on how it should look yet, it's easy to change when we do. 2) New script: smb-enum-groups.nse. Enumerate the local groups on a system and their membership.
This commit is contained in:
476
nselib/msrpc.lua
476
nselib/msrpc.lua
@@ -1326,6 +1326,280 @@ function samr_querydomaininfo2(smbstate, domain_handle, level)
|
|||||||
return true, result
|
return true, result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Call the <code>EnumDomainAliases</code> function, which retrieves a list of groups for a given domain
|
||||||
|
--
|
||||||
|
--@param smbstate The SMB state table
|
||||||
|
--@param domain_handle The domain_handle, returned by <code>samr_opendomain</code>
|
||||||
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values.
|
||||||
|
function samr_enumdomainaliases(smbstate, domain_handle)
|
||||||
|
local i, j
|
||||||
|
local status, result
|
||||||
|
local arguments
|
||||||
|
local pos, align
|
||||||
|
|
||||||
|
arguments = ''
|
||||||
|
|
||||||
|
-- [in] policy_handle *domain_handle,
|
||||||
|
arguments = arguments .. msrpctypes.marshall_policy_handle(domain_handle)
|
||||||
|
|
||||||
|
-- [in,out,ref] uint32 *resume_handle,
|
||||||
|
arguments = arguments .. msrpctypes.marshall_int32_ptr(nil)
|
||||||
|
|
||||||
|
-- [out,ref] samr_SamArray **sam,
|
||||||
|
-- [in] uint32 max_size, (note: Wireshark says this is flags. Either way..)
|
||||||
|
arguments = arguments .. msrpctypes.marshall_int32(0x400)
|
||||||
|
|
||||||
|
-- [out,ref] uint32 *num_entries
|
||||||
|
|
||||||
|
|
||||||
|
-- Do the call
|
||||||
|
status, result = call_function(smbstate, 0x0f, arguments)
|
||||||
|
if(status ~= true) then
|
||||||
|
return false, result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make arguments easier to use
|
||||||
|
arguments = result['arguments']
|
||||||
|
pos = 1
|
||||||
|
|
||||||
|
-- [in] policy_handle *domain_handle,
|
||||||
|
-- [in,out,ref] uint32 *resume_handle,
|
||||||
|
pos, result['resume_handle'] = msrpctypes.unmarshall_int32(arguments, pos)
|
||||||
|
|
||||||
|
-- [out,ref] samr_SamArray **sam,
|
||||||
|
pos, result['sam'] = msrpctypes.unmarshall_samr_SamArray_ptr(arguments, pos)
|
||||||
|
|
||||||
|
-- [in] uint32 max_size,
|
||||||
|
-- [out,ref] uint32 *num_entries
|
||||||
|
pos, result['num_entries'] = msrpctypes.unmarshall_int32(arguments, pos)
|
||||||
|
|
||||||
|
pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos)
|
||||||
|
if(result['return'] == nil) then
|
||||||
|
return false, "Read off the end of the packet (samr.enumdomainaliases)"
|
||||||
|
end
|
||||||
|
if(result['return'] ~= 0) then
|
||||||
|
return false, smb.get_status_name(result['return']) .. " (samr.enumdomainaliases)"
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Call the <code>EnumDomainAliases</code> function, which retrieves a list of groups for a given domain
|
||||||
|
--
|
||||||
|
--@param smbstate The SMB state table
|
||||||
|
--@param domain_handle The domain_handle, returned by <code>samr_opendomain</code>
|
||||||
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values.
|
||||||
|
function samr_lookupnames(smbstate, domain_handle, names)
|
||||||
|
local i, j
|
||||||
|
local status, result
|
||||||
|
local arguments
|
||||||
|
local pos, align
|
||||||
|
|
||||||
|
arguments = ''
|
||||||
|
|
||||||
|
-- [in,ref] policy_handle *domain_handle,
|
||||||
|
arguments = arguments .. msrpctypes.marshall_policy_handle(domain_handle)
|
||||||
|
|
||||||
|
-- [in,range(0,1000)] uint32 num_names,
|
||||||
|
arguments = arguments .. msrpctypes.marshall_int32(#names)
|
||||||
|
|
||||||
|
-- [in,size_is(1000),length_is(num_names)] lsa_String names[],
|
||||||
|
arguments = arguments .. msrpctypes.marshall_lsa_String_array2(names)
|
||||||
|
|
||||||
|
-- [out,ref] samr_Ids *rids,
|
||||||
|
-- [out,ref] samr_Ids *types
|
||||||
|
|
||||||
|
|
||||||
|
-- Do the call
|
||||||
|
status, result = call_function(smbstate, 0x11, arguments)
|
||||||
|
if(status ~= true) then
|
||||||
|
return false, result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make arguments easier to use
|
||||||
|
arguments = result['arguments']
|
||||||
|
pos = 1
|
||||||
|
|
||||||
|
-- [in,ref] policy_handle *domain_handle,
|
||||||
|
-- [in,range(0,1000)] uint32 num_names,
|
||||||
|
-- [in,size_is(1000),length_is(num_names)] lsa_String names[],
|
||||||
|
-- [out,ref] samr_Ids *rids,
|
||||||
|
pos, result['rids'] = msrpctypes.unmarshall_samr_Ids(arguments, pos)
|
||||||
|
|
||||||
|
-- [out,ref] samr_Ids *types
|
||||||
|
pos, result['types'] = msrpctypes.unmarshall_samr_Ids(arguments, pos)
|
||||||
|
|
||||||
|
pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos)
|
||||||
|
if(result['return'] == nil) then
|
||||||
|
return false, "Read off the end of the packet (samr.lookupnames)"
|
||||||
|
end
|
||||||
|
if(result['return'] == smb.status_codes['NT_STATUS_NONE_MAPPED']) then
|
||||||
|
return false, "Couldn't find any names the host recognized"
|
||||||
|
end
|
||||||
|
|
||||||
|
if(result['return'] ~= 0 and result['return'] ~= smb.status_codes['NT_STATUS_SOME_NOT_MAPPED']) then
|
||||||
|
return false, smb.get_status_name(result['return']) .. " (samr.lookupnames)"
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Call the <code>OpenAlias</code> function, which gets a handle to a group.
|
||||||
|
--
|
||||||
|
--@param smbstate The SMB state table
|
||||||
|
--@param domain_handle The domain_handle, returned by <code>samr_opendomain</code>
|
||||||
|
--@param rid The RID of the alias
|
||||||
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values.
|
||||||
|
function samr_openalias(smbstate, domain_handle, rid)
|
||||||
|
local i, j
|
||||||
|
local status, result
|
||||||
|
local arguments
|
||||||
|
local pos, align
|
||||||
|
|
||||||
|
arguments = ''
|
||||||
|
|
||||||
|
-- [in,ref] policy_handle *domain_handle,
|
||||||
|
arguments = arguments .. msrpctypes.marshall_policy_handle(domain_handle)
|
||||||
|
|
||||||
|
-- [in] samr_AliasAccessMask access_mask,
|
||||||
|
arguments = arguments .. msrpctypes.marshall_int32(0x0002000c) -- Full read permission
|
||||||
|
|
||||||
|
-- [in] uint32 rid,
|
||||||
|
arguments = arguments .. msrpctypes.marshall_int32(rid)
|
||||||
|
|
||||||
|
-- [out,ref] policy_handle *alias_handle
|
||||||
|
|
||||||
|
|
||||||
|
-- Do the call
|
||||||
|
status, result = call_function(smbstate, 0x1b, arguments)
|
||||||
|
if(status ~= true) then
|
||||||
|
return false, result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make arguments easier to use
|
||||||
|
arguments = result['arguments']
|
||||||
|
pos = 1
|
||||||
|
|
||||||
|
-- [in,ref] policy_handle *domain_handle,
|
||||||
|
-- [in] samr_AliasAccessMask access_mask,
|
||||||
|
-- [in] uint32 rid,
|
||||||
|
-- [out,ref] policy_handle *alias_handle
|
||||||
|
pos, result['alias_handle'] = msrpctypes.unmarshall_policy_handle(arguments, pos)
|
||||||
|
|
||||||
|
pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos)
|
||||||
|
if(result['return'] == nil) then
|
||||||
|
return false, "Read off the end of the packet (samr.openalias)"
|
||||||
|
end
|
||||||
|
if(result['return'] ~= 0) then
|
||||||
|
return false, smb.get_status_name(result['return']) .. " (samr.openalias)"
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Call the <code>GetMembersInAlias</code> function, which retrieves a list of users in
|
||||||
|
-- a group.
|
||||||
|
--
|
||||||
|
--@param smbstate The SMB state table
|
||||||
|
--@param alias_handle The alias_handle, returned by <code>samr_openalias</code>
|
||||||
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values.
|
||||||
|
function samr_getmembersinalias(smbstate, alias_handle)
|
||||||
|
local i, j
|
||||||
|
local status, result
|
||||||
|
local arguments
|
||||||
|
local pos, align
|
||||||
|
|
||||||
|
arguments = ''
|
||||||
|
|
||||||
|
-- [in,ref] policy_handle *alias_handle,
|
||||||
|
arguments = arguments .. msrpctypes.marshall_policy_handle(alias_handle)
|
||||||
|
-- [out,ref] lsa_SidArray *sids
|
||||||
|
|
||||||
|
|
||||||
|
-- Do the call
|
||||||
|
status, result = call_function(smbstate, 0x21, arguments)
|
||||||
|
if(status ~= true) then
|
||||||
|
return false, result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make arguments easier to use
|
||||||
|
arguments = result['arguments']
|
||||||
|
pos = 1
|
||||||
|
|
||||||
|
-- [in,ref] policy_handle *alias_handle,
|
||||||
|
-- [out,ref] lsa_SidArray *sids
|
||||||
|
pos, result['sids'] = msrpctypes.unmarshall_lsa_SidArray(arguments, pos)
|
||||||
|
|
||||||
|
pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos)
|
||||||
|
if(result['return'] == nil) then
|
||||||
|
return false, "Read off the end of the packet (samr.getmembersinalias)"
|
||||||
|
end
|
||||||
|
if(result['return'] ~= 0) then
|
||||||
|
return false, smb.get_status_name(result['return']) .. " (samr.getmembersinalias)"
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, result
|
||||||
|
end
|
||||||
|
|
||||||
|
---Call the <code>LookupRids</code> function, which converts a list of RIDs to
|
||||||
|
-- names.
|
||||||
|
--
|
||||||
|
--NOTE: This doesn't appear to work (it generates a fault, despite the packet being properly formatted).
|
||||||
|
--if you ever feel like you need this function, check out <code>lsa_lookupsids2</code>.
|
||||||
|
--
|
||||||
|
--@param smbstate The SMB state table
|
||||||
|
--@param domain_handle The domain_handle, returned by <code>samr_opendomain</code>
|
||||||
|
--@param rids An array of RIDs to look up
|
||||||
|
--@return (status, result) If status is false, result is an error message. Otherwise, result is a table of values.
|
||||||
|
--function samr_lookuprids(smbstate, domain_handle, rids)
|
||||||
|
-- local i, j
|
||||||
|
-- local status, result
|
||||||
|
-- local arguments
|
||||||
|
-- local pos, align
|
||||||
|
--
|
||||||
|
-- arguments = ''
|
||||||
|
--
|
||||||
|
---- [in,ref] policy_handle *domain_handle,
|
||||||
|
-- arguments = arguments .. msrpctypes.marshall_policy_handle(domain_handle)
|
||||||
|
---- [in,range(0,1000)] uint32 num_rids,
|
||||||
|
-- arguments = arguments .. msrpctypes.marshall_int32(#rids)
|
||||||
|
---- [in,size_is(1000),length_is(num_rids)] uint32 rids[],
|
||||||
|
-- arguments = arguments .. msrpctypes.marshall_int32_array(rids)
|
||||||
|
---- [out,ref] lsa_Strings *names,
|
||||||
|
---- [out,ref] samr_Ids *types
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- -- Do the call
|
||||||
|
-- status, result = call_function(smbstate, 0x12, arguments)
|
||||||
|
-- if(status ~= true) then
|
||||||
|
-- return false, result
|
||||||
|
-- end
|
||||||
|
--
|
||||||
|
-- -- Make arguments easier to use
|
||||||
|
-- arguments = result['arguments']
|
||||||
|
-- pos = 1
|
||||||
|
--
|
||||||
|
---- [in,ref] policy_handle *domain_handle,
|
||||||
|
---- [in,range(0,1000)] uint32 num_rids,
|
||||||
|
---- [in,size_is(1000),length_is(num_rids)] uint32 rids[],
|
||||||
|
---- [out,ref] lsa_Strings *names,
|
||||||
|
---- [out,ref] samr_Ids *types
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- pos, result['return'] = msrpctypes.unmarshall_int32(arguments, pos)
|
||||||
|
--stdnse.print_debug("Return = %08x\n", result['return'])
|
||||||
|
-- if(result['return'] == nil) then
|
||||||
|
-- return false, "Read off the end of the packet (samr.getmembersinalias)"
|
||||||
|
-- end
|
||||||
|
-- if(result['return'] ~= 0) then
|
||||||
|
-- return false, smb.get_status_name(result['return']) .. " (samr.getmembersinalias)"
|
||||||
|
-- end
|
||||||
|
--
|
||||||
|
-- return true, result
|
||||||
|
--end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---Call the <code>close</code> function, which closes a handle of any type (for example, domain_handle or connect_handle)
|
---Call the <code>close</code> function, which closes a handle of any type (for example, domain_handle or connect_handle)
|
||||||
--@param smbstate The SMB state table
|
--@param smbstate The SMB state table
|
||||||
--@param handle The handle to close
|
--@param handle The handle to close
|
||||||
@@ -2807,8 +3081,6 @@ end
|
|||||||
function samr_enum_users(host)
|
function samr_enum_users(host)
|
||||||
local i, j
|
local i, j
|
||||||
|
|
||||||
stdnse.print_debug(3, "Entering enum_samr()")
|
|
||||||
|
|
||||||
local smbstate
|
local smbstate
|
||||||
local bind_result, connect4_result, enumdomains_result
|
local bind_result, connect4_result, enumdomains_result
|
||||||
local connect_handle
|
local connect_handle
|
||||||
@@ -2929,11 +3201,199 @@ function samr_enum_users(host)
|
|||||||
-- Stop the SAMR SMB
|
-- Stop the SAMR SMB
|
||||||
stop_smb(smbstate)
|
stop_smb(smbstate)
|
||||||
|
|
||||||
stdnse.print_debug(3, "Leaving enum_samr()")
|
|
||||||
|
|
||||||
return true, response
|
return true, response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function samr_enum_groups(host)
|
||||||
|
local i, j
|
||||||
|
|
||||||
|
stdnse.print_debug(1, "MSRPC: Attempting to enumerate groups on %s", host.ip)
|
||||||
|
-- Create the SMB session
|
||||||
|
local status, smbstate = start_smb(host, SAMR_PATH, true)
|
||||||
|
|
||||||
|
if(status == false) then
|
||||||
|
return false, smbstate
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Bind to SAMR service
|
||||||
|
local status, bind_result = bind(smbstate, SAMR_UUID, SAMR_VERSION, nil)
|
||||||
|
if(status == false) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, bind_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Call connect4()
|
||||||
|
local status, connect4_result = samr_connect4(smbstate, host.ip)
|
||||||
|
if(status == false) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, connect4_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save the connect_handle
|
||||||
|
local connect_handle = connect4_result['connect_handle']
|
||||||
|
|
||||||
|
-- Call EnumDomains()
|
||||||
|
local status, enumdomains_result = samr_enumdomains(smbstate, connect_handle)
|
||||||
|
if(status == false) then
|
||||||
|
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
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, "Couldn't find any domains"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Now, loop through the domains and find the groups
|
||||||
|
local domains = {}
|
||||||
|
for _, domain in ipairs(enumdomains_result['sam']['entries']) do
|
||||||
|
-- Get a handy domain name
|
||||||
|
domain = domain['name']
|
||||||
|
domains[domain] = {}
|
||||||
|
|
||||||
|
-- Call LookupDomain()
|
||||||
|
local status, lookupdomain_result = samr_lookupdomain(smbstate, connect_handle, domain)
|
||||||
|
if(status == false) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, lookupdomain_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save the sid
|
||||||
|
local domain_sid = lookupdomain_result['sid']
|
||||||
|
|
||||||
|
-- Call OpenDomain()
|
||||||
|
local status, opendomain_result = samr_opendomain(smbstate, connect_handle, domain_sid)
|
||||||
|
if(status == false) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, opendomain_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save the domain handle
|
||||||
|
local domain_handle = opendomain_result['domain_handle']
|
||||||
|
|
||||||
|
-- Get a list of groups
|
||||||
|
local status, enumaliases_result = samr_enumdomainaliases(smbstate, domain_handle)
|
||||||
|
if(status == false) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, "Couldn't enumerate groups: " .. enumaliases_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Print some output
|
||||||
|
stdnse.print_debug(1, "MSRPC: Found %d groups in %s", #enumaliases_result['sam']['entries'], domain)
|
||||||
|
|
||||||
|
-- Record the results
|
||||||
|
local group_rids = {}
|
||||||
|
for _, group in ipairs(enumaliases_result['sam']['entries']) do
|
||||||
|
-- The RID
|
||||||
|
local group_rid = group['idx']
|
||||||
|
|
||||||
|
-- Keep a list of just RIDs, for easier lookup after
|
||||||
|
table.insert(group_rids, group_rid)
|
||||||
|
|
||||||
|
-- Save the output, this is what will be returned
|
||||||
|
domains[domain][group_rid] = {}
|
||||||
|
domains[domain][group_rid]['name'] = group['name']
|
||||||
|
end -- Loop over group entries
|
||||||
|
|
||||||
|
for _, group_rid in ipairs(group_rids) do
|
||||||
|
-- Get a handle to the alias
|
||||||
|
local status, openalias_result = samr_openalias(smbstate, domain_handle, group_rid)
|
||||||
|
if(not(status)) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, "Couldn't open handle to group: " .. openalias_result
|
||||||
|
end
|
||||||
|
local group_handle = openalias_result['alias_handle']
|
||||||
|
|
||||||
|
-- Get the members of the group
|
||||||
|
local status, getmembers_result = samr_getmembersinalias(smbstate, group_handle)
|
||||||
|
if(not(status)) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, "Couldn't get members in group: " .. getmembers_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save the SIDs
|
||||||
|
local member_sids = {}
|
||||||
|
if(getmembers_result and getmembers_result.sids and getmembers_result.sids.sids) then
|
||||||
|
-- Set the list of member_sids
|
||||||
|
member_sids = getmembers_result.sids.sids
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Print some output
|
||||||
|
stdnse.print_debug(1, "MSRPC: Adding group '%s' (RID: %d) with %d members", domains[domain][group_rid]['name'], group_rid, #member_sids)
|
||||||
|
|
||||||
|
-- Save the output
|
||||||
|
domains[domain][group_rid]['member_sids'] = member_sids
|
||||||
|
|
||||||
|
-- Close the group
|
||||||
|
samr_close(smbstate, group_handle)
|
||||||
|
end -- Loop over group RIDs
|
||||||
|
|
||||||
|
-- Close the domain handle
|
||||||
|
samr_close(smbstate, domain_handle)
|
||||||
|
|
||||||
|
end -- Domain loop
|
||||||
|
|
||||||
|
-- Close the connect handle
|
||||||
|
samr_close(smbstate, connect_handle)
|
||||||
|
|
||||||
|
-- Stop the SAMR SMB
|
||||||
|
stop_smb(smbstate)
|
||||||
|
|
||||||
|
|
||||||
|
-- Now, we need a handle to LSA (in order to convert the RIDs to users
|
||||||
|
-- Create the SMB session
|
||||||
|
local status, smbstate = start_smb(host, LSA_PATH, true)
|
||||||
|
if(status == false) then
|
||||||
|
return false, smbstate
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Bind to LSA service
|
||||||
|
local status, bind_result = bind(smbstate, LSA_UUID, LSA_VERSION, nil)
|
||||||
|
if(status == false) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, bind_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Open the LSA policy
|
||||||
|
local status, openpolicy2_result = lsa_openpolicy2(smbstate, host.ip)
|
||||||
|
if(status == false) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, openpolicy2_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Loop through the domains
|
||||||
|
for domain, domain_data in pairs(domains) do
|
||||||
|
for group_rid, group in pairs(domain_data) do
|
||||||
|
-- Look up the SIDs
|
||||||
|
local status, lookupsids2_result = lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], group['member_sids'])
|
||||||
|
if(status == false) then
|
||||||
|
stop_smb(smbstate)
|
||||||
|
return false, "Error looking up RIDs: " .. lookupsids2_result
|
||||||
|
end
|
||||||
|
|
||||||
|
if(lookupsids2_result and lookupsids2_result.names and lookupsids2_result.names.names and (#lookupsids2_result.names.names > 0)) then
|
||||||
|
local members = {}
|
||||||
|
for _, resolved_name in ipairs(lookupsids2_result.names.names) do
|
||||||
|
if(resolved_name.sid_type == "SID_NAME_USER") then
|
||||||
|
table.insert(members, resolved_name.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
domains[domain][group_rid]['members'] = members
|
||||||
|
else
|
||||||
|
domains[domain][group_rid]['members'] = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Close the handle
|
||||||
|
lsa_close(smbstate, openpolicy2_result['policy_handle'])
|
||||||
|
|
||||||
|
stop_smb(smbstate)
|
||||||
|
|
||||||
|
return true, domains
|
||||||
|
end
|
||||||
|
|
||||||
---Attempt to enumerate users using LSA functions.
|
---Attempt to enumerate users using LSA functions.
|
||||||
--
|
--
|
||||||
--@param host The host object.
|
--@param host The host object.
|
||||||
@@ -2950,8 +3410,6 @@ function lsa_enum_users(host)
|
|||||||
local response = {}
|
local response = {}
|
||||||
local status, smbstate, bind_result, openpolicy2_result, lookupnames2_result, lookupsids2_result
|
local status, smbstate, bind_result, openpolicy2_result, lookupnames2_result, lookupsids2_result
|
||||||
|
|
||||||
stdnse.print_debug(3, "Entering enum_lsa()")
|
|
||||||
|
|
||||||
-- Create the SMB session
|
-- Create the SMB session
|
||||||
status, smbstate = start_smb(host, LSA_PATH, true)
|
status, smbstate = start_smb(host, LSA_PATH, true)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
@@ -3019,7 +3477,7 @@ function lsa_enum_users(host)
|
|||||||
-- Put the details for each name into an array
|
-- Put the details for each name into an array
|
||||||
-- NOTE: Be sure to mirror any changes here in the next bit!
|
-- NOTE: Be sure to mirror any changes here in the next bit!
|
||||||
for j = 1, #lookupsids2_result['names']['names'], 1 do
|
for j = 1, #lookupsids2_result['names']['names'], 1 do
|
||||||
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
|
if(lookupsids2_result['names']['names'][j]['sid_type'] == "SID_NAME_USER") then
|
||||||
local result = {}
|
local result = {}
|
||||||
result['name'] = lookupsids2_result['names']['names'][j]['name']
|
result['name'] = lookupsids2_result['names']['names'][j]['name']
|
||||||
result['rid'] = 500 + j - 1
|
result['rid'] = 500 + j - 1
|
||||||
@@ -3061,7 +3519,7 @@ function lsa_enum_users(host)
|
|||||||
-- Check if the username matches the rid (one server we discovered returned every user as valid,
|
-- Check if the username matches the rid (one server we discovered returned every user as valid,
|
||||||
-- this is to prevent that infinite loop)
|
-- this is to prevent that infinite loop)
|
||||||
if(tonumber(name) ~= rid) then
|
if(tonumber(name) ~= rid) then
|
||||||
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
|
if(lookupsids2_result['names']['names'][j]['sid_type'] == "SID_NAME_USER") then
|
||||||
local result = {}
|
local result = {}
|
||||||
result['name'] = name
|
result['name'] = name
|
||||||
result['rid'] = rid
|
result['rid'] = rid
|
||||||
@@ -3096,8 +3554,6 @@ function lsa_enum_users(host)
|
|||||||
|
|
||||||
stop_smb(smbstate)
|
stop_smb(smbstate)
|
||||||
|
|
||||||
stdnse.print_debug(3, "Leaving enum_lsa()")
|
|
||||||
|
|
||||||
return true, response
|
return true, response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -744,6 +744,24 @@ function marshall_int32(int32)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Marshall an array of int32 values.
|
||||||
|
--
|
||||||
|
--@param data The array
|
||||||
|
--@return A string representing the marshalled data
|
||||||
|
function marshall_int32_array(data)
|
||||||
|
local result = ""
|
||||||
|
|
||||||
|
result = result .. marshall_int32(0x0400) -- Max count
|
||||||
|
result = result .. marshall_int32(0) -- Offset
|
||||||
|
result = result .. marshall_int32(#data) -- Actual count
|
||||||
|
|
||||||
|
for _, v in ipairs(data) do
|
||||||
|
result = result .. marshall_int32(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
--- Marshall an int16, which has the following format:
|
--- Marshall an int16, which has the following format:
|
||||||
-- <code> [in] uint16 var</code>
|
-- <code> [in] uint16 var</code>
|
||||||
--
|
--
|
||||||
@@ -816,12 +834,10 @@ end
|
|||||||
function unmarshall_int32(data, pos)
|
function unmarshall_int32(data, pos)
|
||||||
local value
|
local value
|
||||||
|
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_int32()"))
|
|
||||||
pos, value = bin.unpack("<I", data, pos)
|
pos, value = bin.unpack("<I", data, pos)
|
||||||
if(value == nil) then
|
if(value == nil) then
|
||||||
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_int32(). Please report!")
|
stdnse.print_debug(1, "MSRPC: ERROR: Ran off the end of a packet in unmarshall_int32(). Please report!")
|
||||||
end
|
end
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_int32()"))
|
|
||||||
|
|
||||||
return pos, value
|
return pos, value
|
||||||
end
|
end
|
||||||
@@ -1082,6 +1098,38 @@ function unmarshall_int8_array_ptr(data, pos, pad)
|
|||||||
return pos, str
|
return pos, str
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Unmarshall an array of int32s.
|
||||||
|
--
|
||||||
|
--@param data The data packet.
|
||||||
|
--@param pos The position within the data.
|
||||||
|
--@return (pos, str) The position, and the resulting string, which cannot be nil.
|
||||||
|
function unmarshall_int32_array(data, pos, count)
|
||||||
|
local maxcount
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
pos, maxcount = unmarshall_int32(data, pos)
|
||||||
|
|
||||||
|
for i = 1, count, 1 do
|
||||||
|
pos, result[i] = unmarshall_int32(data, pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
return pos, result
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Unmarshall a pointer to an array of int32s.
|
||||||
|
--
|
||||||
|
--@param data The data packet.
|
||||||
|
--@param pos The position within the data.
|
||||||
|
--@return (pos, str) The position, and the resulting string, which cannot be nil.
|
||||||
|
function unmarshall_int32_array_ptr(data, pos)
|
||||||
|
local count, array
|
||||||
|
|
||||||
|
pos, count = unmarshall_int32(data, pos)
|
||||||
|
pos, array = unmarshall_ptr(ALL, data, pos, unmarshall_int32_array, {count})
|
||||||
|
|
||||||
|
return pos, array
|
||||||
|
end
|
||||||
|
|
||||||
---Marshalls an NTTIME. This is sent as the number of 1/10 microseconds since 1601; however
|
---Marshalls an NTTIME. This is sent as the number of 1/10 microseconds since 1601; however
|
||||||
-- the internal representation is the number of seconds since 1970. Because doing conversions
|
-- the internal representation is the number of seconds since 1970. Because doing conversions
|
||||||
-- in code is annoying, the user will never have to understand anything besides seconds since
|
-- in code is annoying, the user will never have to understand anything besides seconds since
|
||||||
@@ -1462,7 +1510,6 @@ end
|
|||||||
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
|
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
|
||||||
function unmarshall_dom_sid2(data, pos)
|
function unmarshall_dom_sid2(data, pos)
|
||||||
local i
|
local i
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_dom_sid2()"))
|
|
||||||
|
|
||||||
-- Read the SID from the packet
|
-- Read the SID from the packet
|
||||||
local sid = {}
|
local sid = {}
|
||||||
@@ -1488,7 +1535,6 @@ function unmarshall_dom_sid2(data, pos)
|
|||||||
result = result .. string.format("-%u", sid['sub_auths'][i])
|
result = result .. string.format("-%u", sid['sub_auths'][i])
|
||||||
end
|
end
|
||||||
|
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_dom_sid2()"))
|
|
||||||
return pos, result
|
return pos, result
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1499,13 +1545,7 @@ end
|
|||||||
--@param pos The position within <code>data</code>.
|
--@param pos The position within <code>data</code>.
|
||||||
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
|
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
|
||||||
function unmarshall_dom_sid2_ptr(data, pos)
|
function unmarshall_dom_sid2_ptr(data, pos)
|
||||||
local result
|
return unmarshall_ptr(ALL, data, pos, unmarshall_dom_sid2, {})
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_dom_sid2_ptr()"))
|
|
||||||
|
|
||||||
pos, result = unmarshall_ptr(ALL, data, pos, unmarshall_dom_sid2, {})
|
|
||||||
|
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_dom_sid2_ptr()"))
|
|
||||||
return pos, result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---Marshall a struct with the following definition:
|
---Marshall a struct with the following definition:
|
||||||
@@ -1601,8 +1641,9 @@ end
|
|||||||
--@param str The string to marshall
|
--@param str The string to marshall
|
||||||
--@param max_length [optional] The maximum size of the buffer, in characters, including the null terminator.
|
--@param max_length [optional] The maximum size of the buffer, in characters, including the null terminator.
|
||||||
-- Defaults to the length of the string, including the null.
|
-- Defaults to the length of the string, including the null.
|
||||||
|
--@param do_null [optional] Appends a null to the end of the string. Default false.
|
||||||
--@return A string representing the marshalled data.
|
--@return A string representing the marshalled data.
|
||||||
local function marshall_lsa_String_internal(location, str, max_length)
|
local function marshall_lsa_String_internal(location, str, max_length, do_null)
|
||||||
local length
|
local length
|
||||||
local result = ""
|
local result = ""
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_lsa_String_internal()"))
|
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_lsa_String_internal()"))
|
||||||
@@ -1622,12 +1663,16 @@ local function marshall_lsa_String_internal(location, str, max_length)
|
|||||||
length = string.len(str)
|
length = string.len(str)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if(do_null == nil) then
|
||||||
|
do_null = false
|
||||||
|
end
|
||||||
|
|
||||||
if(location == HEAD or location == ALL) then
|
if(location == HEAD or location == ALL) then
|
||||||
result = result .. bin.pack("<SSA", length * 2, max_length * 2, marshall_ptr(HEAD, marshall_unicode, {str, false, max_length}, str))
|
result = result .. bin.pack("<SSA", length * 2, max_length * 2, marshall_ptr(HEAD, marshall_unicode, {str, do_null, max_length}, str))
|
||||||
end
|
end
|
||||||
|
|
||||||
if(location == BODY or location == ALL) then
|
if(location == BODY or location == ALL) then
|
||||||
result = result .. bin.pack("<A", marshall_ptr(BODY, marshall_unicode, {str, false, max_length}, str))
|
result = result .. bin.pack("<A", marshall_ptr(BODY, marshall_unicode, {str, do_null, max_length}, str))
|
||||||
end
|
end
|
||||||
|
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_lsa_String_internal()"))
|
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_lsa_String_internal()"))
|
||||||
@@ -1706,6 +1751,29 @@ function marshall_lsa_String_array(strings)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Basically the same as <code>marshall_lsa_String_array</code>, except it has a different structure
|
||||||
|
--
|
||||||
|
--@param strings The array of strings to marshall
|
||||||
|
function marshall_lsa_String_array2(strings)
|
||||||
|
local array = {}
|
||||||
|
local result
|
||||||
|
|
||||||
|
for i = 1, #strings, 1 do
|
||||||
|
array[i] = {}
|
||||||
|
array[i]['func'] = marshall_lsa_String_internal
|
||||||
|
array[i]['args'] = {strings[i], nil, nil, false}
|
||||||
|
end
|
||||||
|
|
||||||
|
result = marshall_int32(1000) -- Max length
|
||||||
|
result = result .. marshall_int32(0) -- Offset
|
||||||
|
result = result .. marshall_array(array)
|
||||||
|
|
||||||
|
--require 'nsedebug'
|
||||||
|
--nsedebug.print_hex(result)
|
||||||
|
--os.exit()
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
---Table of SID types.
|
---Table of SID types.
|
||||||
local lsa_SidType =
|
local lsa_SidType =
|
||||||
{
|
{
|
||||||
@@ -1913,7 +1981,6 @@ end
|
|||||||
-- anything.
|
-- anything.
|
||||||
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
|
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
|
||||||
local function unmarshall_lsa_TranslatedSid2(location, data, pos, result)
|
local function unmarshall_lsa_TranslatedSid2(location, data, pos, result)
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_lsa_TranslatedSid2()"))
|
|
||||||
if(result == nil) then
|
if(result == nil) then
|
||||||
result = {}
|
result = {}
|
||||||
end
|
end
|
||||||
@@ -1929,7 +1996,6 @@ local function unmarshall_lsa_TranslatedSid2(location, data, pos, result)
|
|||||||
if(location == BODY or location == ALL) then
|
if(location == BODY or location == ALL) then
|
||||||
end
|
end
|
||||||
|
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_lsa_TranslatedSid2()"))
|
|
||||||
return pos, result
|
return pos, result
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2289,7 +2355,6 @@ end
|
|||||||
function marshall_lsa_SidArray(sids)
|
function marshall_lsa_SidArray(sids)
|
||||||
local result = ""
|
local result = ""
|
||||||
local array = {}
|
local array = {}
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_lsa_SidArray()"))
|
|
||||||
|
|
||||||
result = result .. marshall_int32(#sids)
|
result = result .. marshall_int32(#sids)
|
||||||
|
|
||||||
@@ -2301,10 +2366,47 @@ function marshall_lsa_SidArray(sids)
|
|||||||
|
|
||||||
result = result .. marshall_ptr(ALL, marshall_array, {array}, array)
|
result = result .. marshall_ptr(ALL, marshall_array, {array}, array)
|
||||||
|
|
||||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_lsa_SidArray()"))
|
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Unmarshall a struct with the following definition:
|
||||||
|
-- typedef struct {
|
||||||
|
-- dom_sid2 *sid;
|
||||||
|
-- } lsa_SidPtr;
|
||||||
|
--
|
||||||
|
--@param location The part of the pointer wanted, either HEAD (for the data itself), BODY
|
||||||
|
-- (for nothing, since this isn't a pointer), or ALL (for the data). Generally, unless the
|
||||||
|
-- referent_id is split from the data (for example, in an array), you will want
|
||||||
|
-- ALL.
|
||||||
|
--@param data The data being processed.
|
||||||
|
--@param pos The position within <code>data</code>.
|
||||||
|
--@param result This is required when unmarshalling the BODY section, which always comes after
|
||||||
|
-- unmarshalling the HEAD. It is the result returned for this parameter during the
|
||||||
|
-- HEAD unmarshall. If the referent_id was '0', then this function doesn't unmarshall
|
||||||
|
-- anything.
|
||||||
|
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
|
||||||
|
function unmarshall_lsa_SidPtr(location, data, pos, result)
|
||||||
|
return unmarshall_ptr(location, data, pos, unmarshall_dom_sid2, {}, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Unmarshall a struct with the following definition:
|
||||||
|
--
|
||||||
|
-- typedef [public] struct {
|
||||||
|
-- [range(0,1000)] uint32 num_sids;
|
||||||
|
-- [size_is(num_sids)] lsa_SidPtr *sids;
|
||||||
|
-- } lsa_SidArray;
|
||||||
|
--
|
||||||
|
--@param data The data being processed.
|
||||||
|
--@param pos The position within <code>data</code>.
|
||||||
|
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype.
|
||||||
|
function unmarshall_lsa_SidArray(data, pos)
|
||||||
|
local sidarray = {}
|
||||||
|
|
||||||
|
pos, sidarray['count'] = unmarshall_int32(data, pos)
|
||||||
|
pos, sidarray['sids'] = unmarshall_ptr(ALL, data, pos, unmarshall_array, {sidarray['count'], unmarshall_lsa_SidPtr, {}})
|
||||||
|
|
||||||
|
return pos, sidarray
|
||||||
|
end
|
||||||
|
|
||||||
---Marshall a struct with the following definition:
|
---Marshall a struct with the following definition:
|
||||||
--
|
--
|
||||||
@@ -4140,6 +4242,26 @@ function unmarshall_samr_DomainInfo_ptr(data, pos)
|
|||||||
return pos, result
|
return pos, result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Unmarshall a structure with the following definition:
|
||||||
|
--
|
||||||
|
--<code>
|
||||||
|
-- typedef struct {
|
||||||
|
-- [range(0,1024)] uint32 count;
|
||||||
|
-- [size_is(count)] uint32 *ids;
|
||||||
|
-- } samr_Ids;
|
||||||
|
--</code>
|
||||||
|
--
|
||||||
|
--@param data The data being processed.
|
||||||
|
--@param pos The position within <code>data</code>.
|
||||||
|
--@return (pos, result) The new position in <code>data</code>, and a table representing the datatype. May return
|
||||||
|
-- <code>nil</code> if there was an error.
|
||||||
|
function unmarshall_samr_Ids(data, pos)
|
||||||
|
local array
|
||||||
|
|
||||||
|
pos, array = unmarshall_int32_array_ptr(data, pos)
|
||||||
|
|
||||||
|
return pos, array
|
||||||
|
end
|
||||||
|
|
||||||
----------------------------------
|
----------------------------------
|
||||||
-- SVCCTL
|
-- SVCCTL
|
||||||
@@ -4490,9 +4612,7 @@ function marshall_atsvc_JobInfo(command, time)
|
|||||||
result = result .. marshall_int32(time) -- Job time
|
result = result .. marshall_int32(time) -- Job time
|
||||||
result = result .. marshall_int32(0) -- Day of month
|
result = result .. marshall_int32(0) -- Day of month
|
||||||
result = result .. marshall_int8(0, false) -- Day of week
|
result = result .. marshall_int8(0, false) -- Day of week
|
||||||
--io.write("Length = " .. #result .. "\n")
|
|
||||||
result = result .. marshall_atsvc_Flags("JOB_NONINTERACTIVE") -- Flags
|
result = result .. marshall_atsvc_Flags("JOB_NONINTERACTIVE") -- Flags
|
||||||
--io.write("Length = " .. #result .. "\n")
|
|
||||||
result = result .. marshall_int16(0, false) -- Padding
|
result = result .. marshall_int16(0, false) -- Padding
|
||||||
result = result .. marshall_unicode_ptr(command, true) -- Command
|
result = result .. marshall_unicode_ptr(command, true) -- Command
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,16 @@
|
|||||||
local assert = assert;
|
local assert = assert;
|
||||||
local error = error;
|
local error = error;
|
||||||
local pairs = pairs
|
local pairs = pairs
|
||||||
|
local ipairs = ipairs
|
||||||
local tonumber = tonumber;
|
local tonumber = tonumber;
|
||||||
local type = type
|
local type = type
|
||||||
|
|
||||||
local ceil = math.ceil
|
local ceil = math.ceil
|
||||||
local max = math.max
|
local max = math.max
|
||||||
local format = string.format;
|
local format = string.format;
|
||||||
|
local rep = string.rep
|
||||||
local concat = table.concat;
|
local concat = table.concat;
|
||||||
|
local insert = table.insert;
|
||||||
|
|
||||||
local nmap = require "nmap";
|
local nmap = require "nmap";
|
||||||
|
|
||||||
@@ -259,6 +262,148 @@ function string_or_blank(string, blank)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---Get the indentation symbols at a given level.
|
||||||
|
local function format_get_indent(indent, at_end)
|
||||||
|
local str = ""
|
||||||
|
local had_continue = false
|
||||||
|
|
||||||
|
if(not(at_end)) then
|
||||||
|
str = rep('| ', #indent)
|
||||||
|
else
|
||||||
|
for i = #indent, 1, -1 do
|
||||||
|
if(indent[i] and not(had_continue)) then
|
||||||
|
str = str .. "|_ "
|
||||||
|
else
|
||||||
|
had_continue = true
|
||||||
|
str = str .. "| "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
---Takes a table of output on the commandline and formats it for display to the
|
||||||
|
-- user. This is basically done by converting an array of nested tables into a
|
||||||
|
-- string. In addition to numbered array elements, each table can have a 'name'
|
||||||
|
-- and a 'warning' value. The 'name' will be displayed above the table, and
|
||||||
|
-- 'warning' will be displayed, with a 'WARNING' tag, if and only if debugging
|
||||||
|
-- is enabled.
|
||||||
|
--
|
||||||
|
-- Here's an example of a table:
|
||||||
|
-- <code>
|
||||||
|
-- local domains = {}
|
||||||
|
-- domains['name'] = "DOMAINS"
|
||||||
|
-- table.insert(domains, 'Domain 1')
|
||||||
|
-- table.insert(domains, 'Domain 2')
|
||||||
|
--
|
||||||
|
-- local names = {}
|
||||||
|
-- names['name'] = "NAMES"
|
||||||
|
-- names['warning'] = "Not all names could be determined!"
|
||||||
|
-- table.insert(names, "Name 1")
|
||||||
|
--
|
||||||
|
-- local response = {}
|
||||||
|
-- table.insert(response, "Apple pie")
|
||||||
|
-- table.insert(response, domains)
|
||||||
|
-- table.insert(response, names)
|
||||||
|
--
|
||||||
|
-- return stdnse.format_output(true, response)
|
||||||
|
-- </code>
|
||||||
|
--
|
||||||
|
-- With debugging enabled, this is the output:
|
||||||
|
-- <code>
|
||||||
|
-- Host script results:
|
||||||
|
-- | smb-enum-domains:
|
||||||
|
-- | | Apple pie
|
||||||
|
-- | | DOMAINS
|
||||||
|
-- | | | Domain 1
|
||||||
|
-- | | |_ Domain 2
|
||||||
|
-- | | NAMES (WARNING: Not all names could be determined!)
|
||||||
|
-- |_ |_ |_ Name 1
|
||||||
|
-- </code>
|
||||||
|
--
|
||||||
|
--@param status A boolean value dictating whether or not the script succeeded.
|
||||||
|
-- If status is false, and debugging is enabled, 'ERROR' is prepended
|
||||||
|
-- to every line. If status is false and ebugging is disabled, no output
|
||||||
|
-- occurs.
|
||||||
|
--@param data The table of output.
|
||||||
|
--@param indent Used for indentation on recursive calls; should generally be set to
|
||||||
|
-- nil when callling from a script.
|
||||||
|
function format_output(status, data, indent)
|
||||||
|
-- Don't bother if we don't have any data
|
||||||
|
if(#data == 0) then
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Return a single line of output as-is
|
||||||
|
if(#data == 1 and not(data['name']) and not(data['warning'])) then
|
||||||
|
return data[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If data is nil, die with an error (I keep doing that by accident)
|
||||||
|
assert(data, "No data was passed to format_output()")
|
||||||
|
|
||||||
|
-- Used to put 'ERROR: ' in front of all lines on error messages
|
||||||
|
local prefix = ""
|
||||||
|
-- Initialize the output string to blank (or, if we're at the top, add a newline)
|
||||||
|
local output = ""
|
||||||
|
if(not(indent)) then
|
||||||
|
output = ' \n'
|
||||||
|
end
|
||||||
|
|
||||||
|
if(not(status)) then
|
||||||
|
if(nmap.debugging() < 1) then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
prefix = "ERROR: "
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If a string was passed, turn it into a table
|
||||||
|
if(type(data) == 'string') then
|
||||||
|
data = {data}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make sure we have an indent value
|
||||||
|
indent = indent or {}
|
||||||
|
|
||||||
|
for i, value in ipairs(data) do
|
||||||
|
if(type(value) == 'table') then
|
||||||
|
if(value['name']) then
|
||||||
|
if(value['warning'] and nmap.debugging() > 0) then
|
||||||
|
output = output .. format("%s| %s%s (WARNING: %s)\n", format_get_indent(indent), prefix, value['name'], value['warning'])
|
||||||
|
else
|
||||||
|
output = output .. format("%s| %s%s\n", format_get_indent(indent), prefix, value['name'])
|
||||||
|
end
|
||||||
|
elseif(value['warning'] and nmap.debugging() > 0) then
|
||||||
|
output = output .. format("%s| %s(WARNING: %s)\n", format_get_indent(indent), prefix, value['warning'])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Do a shallow copy of indent
|
||||||
|
local new_indent = {}
|
||||||
|
for _, v in ipairs(indent) do
|
||||||
|
insert(new_indent, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
if(i ~= #data) then
|
||||||
|
insert(new_indent, false)
|
||||||
|
else
|
||||||
|
insert(new_indent, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
output = output .. format_output(status, value, new_indent)
|
||||||
|
|
||||||
|
elseif(type(value) == 'string') then
|
||||||
|
if(i ~= #data) then
|
||||||
|
output = output .. format("%s| %s%s\n", format_get_indent(indent, false), prefix, value)
|
||||||
|
else
|
||||||
|
output = output .. format("%s|_ %s%s\n", format_get_indent(indent, true), prefix, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return output
|
||||||
|
end
|
||||||
|
|
||||||
--- This function allows you to create worker threads that may perform
|
--- This function allows you to create worker threads that may perform
|
||||||
-- network tasks in parallel with your script thread.
|
-- network tasks in parallel with your script thread.
|
||||||
--
|
--
|
||||||
|
|||||||
@@ -35,14 +35,14 @@ and <code>dhcp_parse</code>, with their related functions, can easily be abstrac
|
|||||||
-- Interesting ports on 192.168.1.1:
|
-- Interesting ports on 192.168.1.1:
|
||||||
-- PORT STATE SERVICE
|
-- PORT STATE SERVICE
|
||||||
-- 67/udp open dhcps
|
-- 67/udp open dhcps
|
||||||
-- | dhcp-discover:
|
-- | dhcp-discover:
|
||||||
-- | IP Offered: 192.168.1.100
|
-- | | IP Offered: 192.168.1.101
|
||||||
-- | DHCP Message Type: DHCPOFFER
|
-- | | DHCP Message Type: DHCPOFFER
|
||||||
-- | Server Identifier: 192.168.1.1
|
-- | | Server Identifier: 192.168.1.1
|
||||||
-- | IP Address Lease Time: 1 day, 0:00:00
|
-- | | IP Address Lease Time: 1 day, 0:00:00
|
||||||
-- | Subnet Mask: 255.255.255.0
|
-- | | Subnet Mask: 255.255.255.0
|
||||||
-- | Router: 192.168.1.1
|
-- | | Router: 192.168.1.1
|
||||||
-- |_ Domain Name Server: 208.81.7.10, 208.81.7.14
|
-- |_ |_ Domain Name Server: 208.81.7.10, 208.81.7.14
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
--@args dhcptype The type of DHCP request to make. By default, DHCPDISCOVER is sent, but this argument
|
--@args dhcptype The type of DHCP request to make. By default, DHCPDISCOVER is sent, but this argument
|
||||||
@@ -710,11 +710,7 @@ action = function(host, port)
|
|||||||
local status, results = go(host, port)
|
local status, results = go(host, port)
|
||||||
|
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, results)
|
||||||
return "ERROR: " .. results
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if(results == nil) then
|
if(results == nil) then
|
||||||
@@ -724,25 +720,25 @@ action = function(host, port)
|
|||||||
-- Set the port state to open
|
-- Set the port state to open
|
||||||
nmap.set_port_state(host, port, "open")
|
nmap.set_port_state(host, port, "open")
|
||||||
|
|
||||||
local response = " \n"
|
local response = {}
|
||||||
|
|
||||||
-- Display the results
|
-- Display the results
|
||||||
for i, result in ipairs(results) do
|
for i, result in ipairs(results) do
|
||||||
if(#results ~= 1) then
|
if(#results ~= 1) then
|
||||||
response = response .. string.format("Result %d\n", i)
|
table.insert(response, string.format("Result %d", i))
|
||||||
end
|
end
|
||||||
|
|
||||||
response = response .. string.format(" IP Offered: %s\n", result.yiaddr_str)
|
table.insert(response, string.format("IP Offered: %s", result.yiaddr_str))
|
||||||
for _, v in ipairs(result.options) do
|
for _, v in ipairs(result.options) do
|
||||||
if(type(v['value']) == 'table') then
|
if(type(v['value']) == 'table') then
|
||||||
response = response .. string.format(" %s: %s\n", v['name'], stdnse.strjoin(", ", v['value']))
|
table.insert(response, string.format("%s: %s", v['name'], stdnse.strjoin(", ", v['value'])))
|
||||||
else
|
else
|
||||||
response = response .. string.format(" %s: %s\n", v['name'], v['value'])
|
table.insert(response, string.format("%s: %s\n", v['name'], v['value']))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return response
|
return stdnse.format_output(true, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ Made into an actual bruteforce script (previously, it only tried one username/pa
|
|||||||
-- PORT STATE SERVICE REASON
|
-- PORT STATE SERVICE REASON
|
||||||
-- 21/tcp open ftp syn-ack
|
-- 21/tcp open ftp syn-ack
|
||||||
-- | ftp-brute:
|
-- | ftp-brute:
|
||||||
-- | anonymous: IEUser@
|
-- | | anonymous: IEUser@
|
||||||
-- |_ test: password
|
-- |_ |_ test: password
|
||||||
--
|
--
|
||||||
-- @args userlimit The number of user accounts to try (default: unlimited).
|
-- @args userlimit The number of user accounts to try (default: unlimited).
|
||||||
-- @args passlimit The number of passwords to try (default: unlimited).
|
-- @args passlimit The number of passwords to try (default: unlimited).
|
||||||
@@ -186,25 +186,21 @@ local function go(host, port)
|
|||||||
end
|
end
|
||||||
|
|
||||||
action = function(host, port)
|
action = function(host, port)
|
||||||
|
local response = {}
|
||||||
local status, results = go(host, port)
|
local status, results = go(host, port)
|
||||||
|
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, results)
|
||||||
return "ERROR: " .. results
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if(#results == 0) then
|
if(#results == 0) then
|
||||||
return "No accounts found"
|
return stdnse.format_output(false, "No accounts found")
|
||||||
end
|
end
|
||||||
|
|
||||||
local response = " \n"
|
|
||||||
for i, v in ipairs(results) do
|
for i, v in ipairs(results) do
|
||||||
response = response .. string.format("%s: %s\n", v.user, v.password)
|
table.insert(response, string.format("%s: %s\n", v.user, v.password))
|
||||||
end
|
end
|
||||||
|
|
||||||
return response
|
return stdnse.format_output(true, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,14 @@ for 404 Not Found and the status code returned by the random files).
|
|||||||
-- Interesting ports on test.skullsecurity.org (208.81.2.52):
|
-- Interesting ports on test.skullsecurity.org (208.81.2.52):
|
||||||
-- PORT STATE SERVICE REASON
|
-- PORT STATE SERVICE REASON
|
||||||
-- 80/tcp open http syn-ack
|
-- 80/tcp open http syn-ack
|
||||||
-- | http-enum:
|
-- | http-enum:
|
||||||
-- | /icons/ Icons and images
|
-- | | /icons/: Icons and images
|
||||||
-- |_ /x_logo.gif Xerox Phaser Printer
|
-- | | /images/: Icons and images
|
||||||
|
-- | | /robots.txt: Robots file
|
||||||
|
-- | | /sw/auth/login.aspx: Citrix WebTop
|
||||||
|
-- | | /images/outlook.jpg: Outlook Web Access
|
||||||
|
-- | | /nfservlets/servlet/SPSRouterServlet/: netForensics
|
||||||
|
-- |_ |_ /nfservlets/servlet/SPSRouterServlet/: netForensics
|
||||||
--
|
--
|
||||||
--
|
--
|
||||||
--@args displayall Set to '1' or 'true' to display all status codes that may indicate a valid page, not just
|
--@args displayall Set to '1' or 'true' to display all status codes that may indicate a valid page, not just
|
||||||
@@ -223,7 +228,7 @@ end
|
|||||||
|
|
||||||
action = function(host, port)
|
action = function(host, port)
|
||||||
|
|
||||||
local response = " \n"
|
local response = {}
|
||||||
|
|
||||||
-- Add URLs from external files
|
-- Add URLs from external files
|
||||||
local URLs = get_fingerprints()
|
local URLs = get_fingerprints()
|
||||||
@@ -231,11 +236,7 @@ action = function(host, port)
|
|||||||
-- Check what response we get for a 404
|
-- Check what response we get for a 404
|
||||||
local result, result_404, known_404 = http.identify_404(host, port)
|
local result, result_404, known_404 = http.identify_404(host, port)
|
||||||
if(result == false) then
|
if(result == false) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, result_404)
|
||||||
return "ERROR: " .. result_404
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if we can use HEAD requests
|
-- Check if we can use HEAD requests
|
||||||
@@ -245,11 +246,7 @@ action = function(host, port)
|
|||||||
if(use_head == false) then
|
if(use_head == false) then
|
||||||
local result, err = http.can_use_get(host, port)
|
local result, err = http.can_use_get(host, port)
|
||||||
if(result == false) then
|
if(result == false) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, err)
|
||||||
return "ERROR: " .. err
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -303,11 +300,7 @@ action = function(host, port)
|
|||||||
-- Check for http.pipeline error
|
-- Check for http.pipeline error
|
||||||
if(results == nil) then
|
if(results == nil) then
|
||||||
stdnse.print_debug(1, "http-enum.nse: http.pipeline returned nil")
|
stdnse.print_debug(1, "http-enum.nse: http.pipeline returned nil")
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, "http.pipeline returned nil")
|
||||||
return "ERROR: http.pipeline returned nil"
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for i, data in pairs(results) do
|
for i, data in pairs(results) do
|
||||||
@@ -325,15 +318,11 @@ action = function(host, port)
|
|||||||
end
|
end
|
||||||
|
|
||||||
stdnse.print_debug("Found a valid page! (%s)%s", description, status)
|
stdnse.print_debug("Found a valid page! (%s)%s", description, status)
|
||||||
|
|
||||||
response = response .. string.format("%s%s\n", description, status)
|
table.insert(response, string.format("%s%s", description, status))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if string.len(response) > 2 then
|
return stdnse.format_output(true, response)
|
||||||
return response
|
|
||||||
end
|
|
||||||
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,17 +7,21 @@ Performs a GET request for the root folder ("/") of a web server and displays th
|
|||||||
-- Interesting ports on scanme.nmap.org (64.13.134.52):
|
-- Interesting ports on scanme.nmap.org (64.13.134.52):
|
||||||
-- PORT STATE SERVICE
|
-- PORT STATE SERVICE
|
||||||
-- 80/tcp open http syn-ack
|
-- 80/tcp open http syn-ack
|
||||||
-- | http-headers: (HEAD used)
|
-- | http-headers:
|
||||||
-- | HTTP/1.1 200 OK
|
-- | | HTTP/1.1 200 OK
|
||||||
-- | Date: Thu, 27 Aug 2009 15:46:39 GMT
|
-- | | Date: Tue, 10 Nov 2009 01:25:11 GMT
|
||||||
-- | Server: Apache/2.2.11 (Unix) PHP/5.2.8
|
-- | | Server: Apache/2.2.9 (Unix) PHP/5.2.10
|
||||||
-- | Connection: close
|
-- | | Last-Modified: Sat, 11 Oct 2008 15:22:21 GMT
|
||||||
-- |_ Content-Type: text/html;charset=ISO-8859-1
|
-- | | ETag: "90013-e3d-458fbd508c540"
|
||||||
|
-- | | Accept-Ranges: bytes
|
||||||
|
-- | | Content-Length: 3645
|
||||||
|
-- | | Connection: close
|
||||||
|
-- | | Content-Type: text/html
|
||||||
|
-- |_ |_ (Request type: HEAD)
|
||||||
--
|
--
|
||||||
--@args path The path to request, such as '/index.php'. Default: '/'.
|
--@args path The path to request, such as '/index.php'. Default: '/'.
|
||||||
--@args useget Set to force GET requests instead of HEAD.
|
--@args useget Set to force GET requests instead of HEAD.
|
||||||
|
|
||||||
|
|
||||||
author = "Ron Bowes <ron@skullsecurity.org>"
|
author = "Ron Bowes <ron@skullsecurity.org>"
|
||||||
|
|
||||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
@@ -81,11 +85,11 @@ action = function(host, port)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local response = "(" .. request_type .. " used)\n"
|
table.insert(result.rawheader, "(Request type: " .. request_type .. ")")
|
||||||
for _, header in ipairs(result.rawheader) do
|
-- for _, header in ipairs(result.rawheader) do
|
||||||
response = response .. header .. "\n"
|
-- response = response .. header .. "\n"
|
||||||
end
|
-- end
|
||||||
|
|
||||||
return response
|
return stdnse.format_output(true, result.rawheader)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ Thanks to Denis from the above link for finding this technique!
|
|||||||
-- Interesting ports on www.sopharma.bg (84.242.167.49):
|
-- Interesting ports on www.sopharma.bg (84.242.167.49):
|
||||||
-- PORT STATE SERVICE REASON
|
-- PORT STATE SERVICE REASON
|
||||||
-- 80/tcp open http syn-ack
|
-- 80/tcp open http syn-ack
|
||||||
-- |_ http-infected: Host appears to be clean
|
-- |_ http-malware-host: Host appears to be clean
|
||||||
-- 8080/tcp open http-proxy syn-ack
|
-- 8080/tcp open http-proxy syn-ack
|
||||||
-- | http-malware-host: Host appears to be infected (/ts/in.cgi?open2 redirects to http://last-another-life.ru:8080/index.php)
|
-- | http-malware-host:
|
||||||
-- |_ See: http://blog.unmaskparasites.com/2009/09/11/dynamic-dns-and-botnet-of-zombie-web-servers/
|
-- | | Host appears to be infected (/ts/in.cgi?open2 redirects to http://last-another-life.ru:8080/index.php)
|
||||||
|
-- |_ |_ See: http://blog.unmaskparasites.com/2009/09/11/dynamic-dns-and-botnet-of-zombie-web-servers/
|
||||||
--
|
--
|
||||||
|
|
||||||
author = "Ron Bowes <ron@skullsecurity.net>"
|
author = "Ron Bowes <ron@skullsecurity.net>"
|
||||||
@@ -45,46 +46,40 @@ portrule = function(host, port)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function go(host, port)
|
action = function(host, port)
|
||||||
-- Check what response we get for a 404
|
-- Check what response we get for a 404
|
||||||
local result, result_404, known_404 = http.identify_404(host, port)
|
local result, result_404, known_404 = http.identify_404(host, port)
|
||||||
if(result == false) then
|
if(result == false) then
|
||||||
return false, "Couldn't identify 404 message: " .. result_404
|
return stdnse.format_output(false, "Couldn't identify 404 message: " .. result_404)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If the 404 result is a 302, we're going to have trouble
|
-- If the 404 result is a 302, we're going to have trouble
|
||||||
if(result_404 == 302) then
|
if(result_404 == 302) then
|
||||||
return false, "Unknown pages return a 302 response; unable to check"
|
return stdnse.format_output(false, "Unknown pages return a 302 response; unable to check")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Perform a GET request on the file
|
-- Perform a GET request on the file
|
||||||
result = http.get_url("http://" .. host.ip .. ":" .. port.number .. "/ts/in.cgi?open2")
|
result = http.get_url("http://" .. host.ip .. ":" .. port.number .. "/ts/in.cgi?open2")
|
||||||
if(result == nil) then
|
if(not(result)) then
|
||||||
return false, "Couldn't perform GET request"
|
return stdnse.format_output(false, "Couldn't perform GET request")
|
||||||
end
|
end
|
||||||
|
|
||||||
if(result.status == 302) then
|
if(result.status == 302) then
|
||||||
|
local response = {}
|
||||||
if(result.header.location) then
|
if(result.header.location) then
|
||||||
return true, string.format("Host appears to be infected (/ts/in.cgi?open2 redirects to %s)\nSee: http://blog.unmaskparasites.com/2009/09/11/dynamic-dns-and-botnet-of-zombie-web-servers/", result.header.location)
|
table.insert(response, string.format("Host appears to be infected (/ts/in.cgi?open2 redirects to %s)", result.header.location))
|
||||||
else
|
else
|
||||||
return true, string.format("Host appears to be infected (/ts/in.cgi?open2 return a redirect)\nSee: http://blog.unmaskparasites.com/2009/09/11/dynamic-dns-and-botnet-of-zombie-web-servers/")
|
table.insert(response, "Host appears to be infected (/ts/in.cgi?open2 return a redirect")
|
||||||
end
|
end
|
||||||
|
table.insert(response, "See: http://blog.unmaskparasites.com/2009/09/11/dynamic-dns-and-botnet-of-zombie-web-servers/")
|
||||||
|
return stdnse.format_output(true, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
return true, nil
|
-- Not infected
|
||||||
end
|
if(nmap.verbosity() > 0) then
|
||||||
|
return "Host appears to be clean"
|
||||||
action = function(host, port)
|
else
|
||||||
local status, result = go(host, port)
|
return nil
|
||||||
|
|
||||||
if(status == false) then
|
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return "ERROR: " .. result
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -11,19 +11,20 @@ owns.
|
|||||||
-- sudo nmap -sU --script nbstat.nse -p137 <host>\n
|
-- sudo nmap -sU --script nbstat.nse -p137 <host>\n
|
||||||
--
|
--
|
||||||
-- @output
|
-- @output
|
||||||
-- (no verbose)\n
|
-- Host script results:
|
||||||
-- |_ nbstat: NetBIOS name: TST, NetBIOS user: RON, NetBIOS MAC: 00:0c:29:f9:d9:28\n
|
-- |_ nbstat: NetBIOS name: WINDOWS2003, NetBIOS user: <unknown>, NetBIOS MAC: 00:0c:29:c6:da:f5
|
||||||
--\n
|
--
|
||||||
-- (verbose)\n
|
-- Host script results:
|
||||||
-- | nbstat: NetBIOS name: TST, NetBIOS user: RON, NetBIOS MAC: 00:0c:29:f9:d9:28\n
|
-- | nbstat:
|
||||||
-- | Name: TST<00> Flags: <unique><active>\n
|
-- | | NetBIOS name: WINDOWS2003, NetBIOS user: <unknown>, NetBIOS MAC: 00:0c:29:c6:da:f5
|
||||||
-- | Name: TST<20> Flags: <unique><active>\n
|
-- | | Names
|
||||||
-- | Name: WORKGROUP<00> Flags: <group><active>\n
|
-- | | | WINDOWS2003<00> Flags: <unique><active>
|
||||||
-- | Name: TST<03> Flags: <unique><active>\n
|
-- | | | WINDOWS2003<20> Flags: <unique><active>
|
||||||
-- | Name: WORKGROUP<1e> Flags: <group><active>\n
|
-- | | | SKULLSECURITY<00> Flags: <group><active>
|
||||||
-- | Name: RON<03> Flags: <unique><active>\n
|
-- | | | SKULLSECURITY<1e> Flags: <group><active>
|
||||||
-- | Name: WORKGROUP<1d> Flags: <unique><active>\n
|
-- | | | SKULLSECURITY<1d> Flags: <unique><active>
|
||||||
-- |_ Name: \x01\x02__MSBROWSE__\x02<01> Flags: <group><active>\n
|
-- |_ |_ |_ \x01\x02__MSBROWSE__\x02<01> Flags: <group><active>
|
||||||
|
|
||||||
|
|
||||||
author = "Brandon Enright <bmenrigh@ucsd.edu>, Ron Bowes"
|
author = "Brandon Enright <bmenrigh@ucsd.edu>, 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"
|
||||||
@@ -67,7 +68,7 @@ action = function(host)
|
|||||||
local names, statistics
|
local names, statistics
|
||||||
local server_name, user_name
|
local server_name, user_name
|
||||||
local mac
|
local mac
|
||||||
local result = ""
|
local response = {}
|
||||||
|
|
||||||
-- Get the list of NetBIOS names
|
-- Get the list of NetBIOS names
|
||||||
status, names, statistics = netbios.do_nbstat(host.ip)
|
status, names, statistics = netbios.do_nbstat(host.ip)
|
||||||
@@ -75,19 +76,19 @@ action = function(host)
|
|||||||
status, names, statistics = netbios.do_nbstat(host.ip)
|
status, names, statistics = netbios.do_nbstat(host.ip)
|
||||||
status, names, statistics = netbios.do_nbstat(host.ip)
|
status, names, statistics = netbios.do_nbstat(host.ip)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
return "ERROR: " .. names
|
return stdnse.format_output(false, names)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get the server name
|
-- Get the server name
|
||||||
status, server_name = netbios.get_server_name(host.ip, names)
|
status, server_name = netbios.get_server_name(host.ip, names)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
return "ERROR: " .. server_name
|
return stdnse.format_output(false, server_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get the logged in user
|
-- Get the logged in user
|
||||||
status, user_name = netbios.get_user_name(host.ip, names)
|
status, user_name = netbios.get_user_name(host.ip, names)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
return "ERROR: " .. user_name
|
return stdnse.format_output(false, user_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Format the Mac address in the standard way
|
-- Format the Mac address in the standard way
|
||||||
@@ -106,27 +107,40 @@ action = function(host)
|
|||||||
user_name = "<unknown>"
|
user_name = "<unknown>"
|
||||||
end
|
end
|
||||||
|
|
||||||
result = result .. string.format("NetBIOS name: %s, NetBIOS user: %s, NetBIOS MAC: %s\n", server_name, user_name, mac)
|
|
||||||
|
|
||||||
-- If verbosity is set, dump the whole list of names
|
-- If verbosity is set, dump the whole list of names
|
||||||
if(nmap.verbosity() >= 1) then
|
if(nmap.verbosity() >= 1) then
|
||||||
|
table.insert(response, string.format("NetBIOS name: %s, NetBIOS user: %s, NetBIOS MAC: %s\n", server_name, user_name, mac))
|
||||||
|
|
||||||
|
local names_output = {}
|
||||||
|
names_output['name'] = "Names"
|
||||||
for i = 1, #names, 1 do
|
for i = 1, #names, 1 do
|
||||||
local padding = string.rep(" ", 17 - string.len(names[i]['name']))
|
local padding = string.rep(" ", 17 - string.len(names[i]['name']))
|
||||||
local flags_str = netbios.flags_to_string(names[i]['flags'])
|
local flags_str = netbios.flags_to_string(names[i]['flags'])
|
||||||
result = result .. string.format("Name: %s<%02x>%sFlags: %s\n", names[i]['name'], names[i]['suffix'], padding, flags_str)
|
table.insert(names_output, string.format("%s<%02x>%sFlags: %s\n", names[i]['name'], names[i]['suffix'], padding, flags_str))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
table.insert(response, names_output)
|
||||||
|
|
||||||
-- If super verbosity is set, print out the full statistics
|
-- If super verbosity is set, print out the full statistics
|
||||||
if(nmap.verbosity() >= 2) then
|
if(nmap.verbosity() >= 2) then
|
||||||
result = result .. "Statistics: "
|
local statistics_output = {}
|
||||||
|
local statistics_string = ''
|
||||||
|
statistics_output['name'] = "Statistics"
|
||||||
for i = 1, #statistics, 1 do
|
for i = 1, #statistics, 1 do
|
||||||
result = result .. string.format("%02x ", statistics:byte(i))
|
statistics_string = statistics_string .. string.format("%02x ", statistics:byte(i))
|
||||||
|
if(i ~= #statistics and ((i) % 16) == 0) then
|
||||||
|
table.insert(statistics_output, statistics_string)
|
||||||
|
statistics_string = ''
|
||||||
|
end
|
||||||
end
|
end
|
||||||
result = result .. "\n"
|
table.insert(statistics_output, statistics_string)
|
||||||
|
table.insert(response, statistics_output)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return stdnse.format_output(true, response)
|
||||||
|
else
|
||||||
|
return string.format("NetBIOS name: %s, NetBIOS user: %s, NetBIOS MAC: %s\n", server_name, user_name, mac)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -530,10 +530,10 @@ local function conficker_check(ip, port, protocol)
|
|||||||
return true, "Received valid data", result
|
return true, "Received valid data", result
|
||||||
end
|
end
|
||||||
|
|
||||||
local function go(host)
|
action = function(host)
|
||||||
local tcp_ports = {}
|
local tcp_ports = {}
|
||||||
local udp_ports = {}
|
local udp_ports = {}
|
||||||
local response = " \n"
|
local response = {}
|
||||||
local i
|
local i
|
||||||
local port, protocol
|
local port, protocol
|
||||||
local count = 0
|
local count = 0
|
||||||
@@ -553,10 +553,6 @@ local function go(host)
|
|||||||
udp_ports[i] = true
|
udp_ports[i] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- if((i % 10) == 0) then
|
|
||||||
-- io.write(i .. "\n")
|
|
||||||
-- end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -582,7 +578,7 @@ local function go(host)
|
|||||||
udp_ports[generated_ports[2]] = true
|
udp_ports[generated_ports[2]] = true
|
||||||
udp_ports[generated_ports[4]] = true
|
udp_ports[generated_ports[4]] = true
|
||||||
|
|
||||||
response = string.format("Checking for Conficker.C or higher...\n")
|
table.insert(response, string.format("Checking for Conficker.C or higher..."))
|
||||||
|
|
||||||
-- Check the TCP ports
|
-- Check the TCP ports
|
||||||
for port in pairs(tcp_ports) do
|
for port in pairs(tcp_ports) do
|
||||||
@@ -592,10 +588,10 @@ local function go(host)
|
|||||||
checks = checks + 1
|
checks = checks + 1
|
||||||
|
|
||||||
if(status == true) then
|
if(status == true) then
|
||||||
response = response .. string.format("| Check %d (port %d/%s): INFECTED (%s)\n", checks, port, "tcp", reason)
|
table.insert(response, string.format("Check %d (port %d/%s): INFECTED (%s)", checks, port, "tcp", reason))
|
||||||
count = count + 1
|
count = count + 1
|
||||||
else
|
else
|
||||||
response = response .. string.format("| Check %d (port %d/%s): CLEAN (%s)\n", checks, port, "tcp", reason)
|
table.insert(response, string.format("Check %d (port %d/%s): CLEAN (%s)", checks, port, "tcp", reason))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -607,43 +603,24 @@ local function go(host)
|
|||||||
checks = checks + 1
|
checks = checks + 1
|
||||||
|
|
||||||
if(status == true) then
|
if(status == true) then
|
||||||
response = response .. string.format("| Check %d (port %d/%s): INFECTED (%s)\n", checks, port, "udp", reason)
|
table.insert(response, string.format("Check %d (port %d/%s): INFECTED (%s)", checks, port, "udp", reason))
|
||||||
count = count + 1
|
count = count + 1
|
||||||
else
|
else
|
||||||
response = response .. string.format("| Check %d (port %d/%s): CLEAN (%s)\n", checks, port, "udp", reason)
|
table.insert(response, string.format("| Check %d (port %d/%s): CLEAN (%s)", checks, port, "udp", reason))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Remove the response if verbose is turned off
|
-- Check how many INFECTED hits we got
|
||||||
if(count == 0 and nmap.verbosity() < 2) then
|
if(count == 0) then
|
||||||
response = ""
|
if (nmap.verbosity() > 1) then
|
||||||
|
table.insert(response, string.format("%d/%d checks are positive: Host is CLEAN or ports are blocked", count, checks))
|
||||||
|
else
|
||||||
|
response = ''
|
||||||
|
end
|
||||||
else
|
else
|
||||||
response = response .. "|_ "
|
table.insert(response, string.format("%d/%d checks are positive: Host is likely INFECTED", count, checks))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check how many INFECTED hits we got
|
|
||||||
if(count == 0) then
|
|
||||||
if (nmap.verbosity() > 1) then
|
|
||||||
response = response .. string.format("%d/%d checks are positive: Host is CLEAN or ports are blocked\n", count, checks)
|
|
||||||
else
|
|
||||||
response = nil
|
|
||||||
end
|
|
||||||
else
|
|
||||||
response = response .. string.format("%d/%d checks are positive: Host is likely INFECTED\n", count, checks)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
return true, stdnse.format_output(true, response)
|
||||||
return true, response
|
|
||||||
end
|
end
|
||||||
|
|
||||||
action = function(host)
|
|
||||||
local status, result = go(host)
|
|
||||||
|
|
||||||
if(status == false) then
|
|
||||||
return "ERROR: " .. result
|
|
||||||
else
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ Entry { filename = "skypev2-version.nse", categories = { "version", } }
|
|||||||
Entry { filename = "smb-brute.nse", categories = { "auth", "intrusive", } }
|
Entry { filename = "smb-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
Entry { filename = "smb-check-vulns.nse", categories = { "dos", "exploit", "intrusive", "vuln", } }
|
Entry { filename = "smb-check-vulns.nse", categories = { "dos", "exploit", "intrusive", "vuln", } }
|
||||||
Entry { filename = "smb-enum-domains.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "smb-enum-domains.nse", categories = { "discovery", "intrusive", } }
|
||||||
|
Entry { filename = "smb-enum-groups.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "smb-enum-processes.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "smb-enum-processes.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "smb-enum-sessions.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "smb-enum-sessions.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "smb-enum-shares.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "smb-enum-shares.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "smb-enum-users.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "smb-enum-users.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "smb-os-discovery.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "smb-os-discovery.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "smb-psexec.nse", categories = { "intrusive", } }
|
Entry { filename = "smb-psexec.nse", categories = { "intrusive", } }
|
||||||
Entry { filename = "smb-pwdump.nse", categories = { "intrusive", } }
|
|
||||||
Entry { filename = "smb-security-mode.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "smb-security-mode.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "smb-server-stats.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "smb-server-stats.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "smb-system-info.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "smb-system-info.nse", categories = { "discovery", "intrusive", } }
|
||||||
|
|||||||
@@ -67,16 +67,16 @@ determined with a fairly efficient bruteforce. For example, if the actual passwo
|
|||||||
--@output
|
--@output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-brute:
|
-- | smb-brute:
|
||||||
-- | bad name:test => Login was successful
|
-- | | bad name:test => Login was successful
|
||||||
-- | consoletest:test => Password was correct, but user can't log in without changing it
|
-- | | consoletest:test => Password was correct, but user can't log in without changing it
|
||||||
-- | guest:<anything> => Password was correct, but user's account is disabled
|
-- | | guest:<anything> => Password was correct, but user's account is disabled
|
||||||
-- | mixcase:BuTTeRfLY1 => Login was successful
|
-- | | mixcase:BuTTeRfLY1 => Login was successful
|
||||||
-- | test:password1 => Login was successful
|
-- | | test:password1 => Login was successful
|
||||||
-- | this:password => Login was successful
|
-- | | this:password => Login was successful
|
||||||
-- | thisisaverylong:password => Login was successful
|
-- | | thisisaverylong:password => Login was successful
|
||||||
-- | thisisaverylongname:password => Login was successful
|
-- | | thisisaverylongname:password => Login was successful
|
||||||
-- | thisisaverylongnamev:password => Login was successful
|
-- | | thisisaverylongnamev:password => Login was successful
|
||||||
-- |_ web:TeSt => Password was correct, but user's account is disabled
|
-- |_ |_ web:TeSt => Password was correct, but user's account is disabled
|
||||||
--
|
--
|
||||||
-- @args smblockout Unless this is set to '1' or 'true', the script won't continue if it
|
-- @args smblockout Unless this is set to '1' or 'true', the script won't continue if it
|
||||||
-- locks out an account or thinks it will lock out an account.
|
-- locks out an account or thinks it will lock out an account.
|
||||||
@@ -1001,7 +1001,7 @@ action = function(host, port)
|
|||||||
-- TRACEBACK[coroutine.running()] = true;
|
-- TRACEBACK[coroutine.running()] = true;
|
||||||
|
|
||||||
local status, result
|
local status, result
|
||||||
local response = " \n"
|
local response = {}
|
||||||
|
|
||||||
local username
|
local username
|
||||||
local usernames = {}
|
local usernames = {}
|
||||||
@@ -1010,11 +1010,7 @@ action = function(host, port)
|
|||||||
|
|
||||||
status, result, locked_result = go(host)
|
status, result, locked_result = go(host)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, result)
|
||||||
return "ERROR: " .. result
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Put the usernames in their own table
|
-- Put the usernames in their own table
|
||||||
@@ -1027,11 +1023,11 @@ action = function(host, port)
|
|||||||
|
|
||||||
-- Display the usernames
|
-- Display the usernames
|
||||||
if(#usernames == 0) then
|
if(#usernames == 0) then
|
||||||
response = "No accounts found\n"
|
table.insert(response, "No accounts found")
|
||||||
else
|
else
|
||||||
for i=1, #usernames, 1 do
|
for i=1, #usernames, 1 do
|
||||||
local username = usernames[i]
|
local username = usernames[i]
|
||||||
response = response .. format_result(username, result[username]['password'], result[username]['result']) .. "\n"
|
table.insert(response, format_result(username, result[username]['password'], result[username]['result']))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1044,9 +1040,9 @@ action = function(host, port)
|
|||||||
table.sort(locked)
|
table.sort(locked)
|
||||||
|
|
||||||
-- Display the list
|
-- Display the list
|
||||||
response = response .. string.format("Locked accounts found: %s\n", stdnse.strjoin(", ", locked))
|
table.insert(response, string.format("Locked accounts found: %s", stdnse.strjoin(", ", locked)))
|
||||||
end
|
end
|
||||||
|
|
||||||
return response
|
return stdnse.format_output(true, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -63,10 +63,10 @@ on the Nmap-dev mailing list and I'll add it to my list [Ron Bowes]).
|
|||||||
--@output
|
--@output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-check-vulns:
|
-- | smb-check-vulns:
|
||||||
-- | MS08-067: FIXED
|
-- | | MS08-067: NOT VULNERABLE
|
||||||
-- | Conficker: Likely INFECTED
|
-- | | Conficker: Likely CLEAN
|
||||||
-- | regsvc DoS: FIXED
|
-- | | regsvc DoS: NOT VULNERABLE
|
||||||
-- |_ SMBv2 DoS (CVE-2009-3103): VULNERABLE
|
-- |_ |_ SMBv2 DoS (CVE-2009-3103): NOT VULNERABLE
|
||||||
--
|
--
|
||||||
-- @args unsafe If set, this script will run checks that, if the system isn't
|
-- @args unsafe If set, this script will run checks that, if the system isn't
|
||||||
-- patched, are basically guaranteed to crash something. Remember that
|
-- patched, are basically guaranteed to crash something. Remember that
|
||||||
@@ -407,9 +407,9 @@ local function get_response(check, message, description, minimum_verbosity, mini
|
|||||||
-- Check if we have appropriate verbosity/debug
|
-- Check if we have appropriate verbosity/debug
|
||||||
if(nmap.verbosity() >= minimum_verbosity and nmap.debugging() >= minimum_debug) then
|
if(nmap.verbosity() >= minimum_verbosity and nmap.debugging() >= minimum_debug) then
|
||||||
if(description == nil or description == '') then
|
if(description == nil or description == '') then
|
||||||
return string.format("%s: %s\n", check, message)
|
return string.format("%s: %s", check, message)
|
||||||
else
|
else
|
||||||
return string.format("%s: %s (%s)\n", check, message, description)
|
return string.format("%s: %s (%s)", check, message, description)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
return ''
|
return ''
|
||||||
@@ -419,23 +419,23 @@ end
|
|||||||
action = function(host)
|
action = function(host)
|
||||||
|
|
||||||
local status, result, message
|
local status, result, message
|
||||||
local response = ""
|
local response = {}
|
||||||
|
|
||||||
-- Check for ms08-067
|
-- Check for ms08-067
|
||||||
status, result, message = check_ms08_067(host)
|
status, result, message = check_ms08_067(host)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
response = response .. get_response("MS08-067", "ERROR", result, 0, 1)
|
table.insert(response, get_response("MS08-067", "ERROR", result, 0, 1))
|
||||||
else
|
else
|
||||||
if(result == VULNERABLE) then
|
if(result == VULNERABLE) then
|
||||||
response = response .. get_response("MS08-067", "VULNERABLE", nil, 0)
|
table.insert(response, get_response("MS08-067", "VULNERABLE", nil, 0))
|
||||||
elseif(result == UNKNOWN) then
|
elseif(result == UNKNOWN) then
|
||||||
response = response .. get_response("MS08-067", "LIKELY VULNERABLE", "host stopped responding", 1) -- TODO: this isn't very accurate
|
table.insert(response, get_response("MS08-067", "LIKELY VULNERABLE", "host stopped responding", 1)) -- TODO: this isn't very accurate
|
||||||
elseif(result == NOTRUN) then
|
elseif(result == NOTRUN) then
|
||||||
response = response .. get_response("MS08-067", "CHECK DISABLED", "remove 'safe=1' argument to run", 1)
|
table.insert(response, get_response("MS08-067", "CHECK DISABLED", "remove 'safe=1' argument to run", 1))
|
||||||
elseif(result == INFECTED) then
|
elseif(result == INFECTED) then
|
||||||
response = response .. get_response("MS08-067", "FIXED", "likely by Conficker", 0)
|
table.insert(response, get_response("MS08-067", "NOT VULNERABLE", "likely by Conficker", 0))
|
||||||
else
|
else
|
||||||
response = response .. get_response("MS08-067", "FIXED", nil, 1)
|
table.insert(response, get_response("MS08-067", "NOT VULNERABLE", nil, 1))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -443,55 +443,48 @@ action = function(host)
|
|||||||
status, result = check_conficker(host)
|
status, result = check_conficker(host)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
local msg = CONFICKER_ERROR_HELP[result] or "UNKNOWN; got error " .. result
|
local msg = CONFICKER_ERROR_HELP[result] or "UNKNOWN; got error " .. result
|
||||||
response = response .. get_response("Conficker", msg, nil, 1) -- Only set verbosity for this, since it might be an error or it might be UNKNOWN
|
table.insert(response, get_response("Conficker", msg, nil, 1)) -- Only set verbosity for this, since it might be an error or it might be UNKNOWN
|
||||||
else
|
else
|
||||||
if(result == CLEAN) then
|
if(result == CLEAN) then
|
||||||
response = response .. get_response("Conficker", "Likely CLEAN", nil, 1)
|
table.insert(response, get_response("Conficker", "Likely CLEAN", nil, 1))
|
||||||
elseif(result == INFECTED) then
|
elseif(result == INFECTED) then
|
||||||
response = response .. get_response("Conficker", "Likely INFECTED", "by Conficker.C or lower", 0)
|
table.insert(response, get_response("Conficker", "Likely INFECTED", "by Conficker.C or lower", 0))
|
||||||
elseif(result == INFECTED2) then
|
elseif(result == INFECTED2) then
|
||||||
response = response .. get_response("Conficker", "Likely INFECTED", "by Conficker.D or higher", 0)
|
table.insert(response, get_response("Conficker", "Likely INFECTED", "by Conficker.D or higher", 0))
|
||||||
else
|
else
|
||||||
response = response .. get_response("Conficker", "UNKNOWN", result, 0, 1)
|
table.insert(response, get_response("Conficker", "UNKNOWN", result, 0, 1))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check for a winreg_Enum crash
|
-- Check for a winreg_Enum crash
|
||||||
status, result = check_winreg_Enum_crash(host)
|
status, result = check_winreg_Enum_crash(host)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
response = response .. get_response("regsvc DoS", "ERROR", result, 0, 1)
|
table.insert(response, get_response("regsvc DoS", "ERROR", result, 0, 1))
|
||||||
else
|
else
|
||||||
if(result == VULNERABLE) then
|
if(result == VULNERABLE) then
|
||||||
response = response .. get_response("regsvc DoS", "VULNERABLE", nil, 0)
|
table.insert(response, get_response("regsvc DoS", "VULNERABLE", nil, 0))
|
||||||
elseif(result == NOTRUN) then
|
elseif(result == NOTRUN) then
|
||||||
response = response .. get_response("regsvc DoS", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1)
|
table.insert(response, get_response("regsvc DoS", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
|
||||||
else
|
else
|
||||||
response = response .. get_response("regsvc DoS", "FIXED", nil, 1)
|
table.insert(response, get_response("regsvc DoS", "NOT VULNERABLE", nil, 1))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check for SMBv2 vulnerablity
|
-- Check for SMBv2 vulnerablity
|
||||||
status, result = check_smbv2_dos(host)
|
status, result = check_smbv2_dos(host)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
response = response .. get_response("SMBv2 DoS (CVE-2009-3103)", "ERROR", result, 0, 1)
|
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "ERROR", result, 0, 1))
|
||||||
else
|
else
|
||||||
if(result == VULNERABLE) then
|
if(result == VULNERABLE) then
|
||||||
response = response .. get_response("SMBv2 DoS (CVE-2009-3103)", "VULNERABLE", nil, 0)
|
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "VULNERABLE", nil, 0))
|
||||||
elseif(result == NOTRUN) then
|
elseif(result == NOTRUN) then
|
||||||
response = response .. get_response("SMBv2 DoS (CVE-2009-3103)", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1)
|
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
|
||||||
else
|
else
|
||||||
response = response .. get_response("SMBv2 DoS (CVE-2009-3103)", "FIXED", nil, 1)
|
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "NOT VULNERABLE", nil, 1))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If we got a response, add a linefeed
|
return stdnse.format_output(true, response)
|
||||||
if(response ~= "") then
|
|
||||||
response = " \n" .. response
|
|
||||||
else
|
|
||||||
response = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
return response
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,26 +32,20 @@ After the initial <code>bind</code> to SAMR, the sequence of calls is:
|
|||||||
--
|
--
|
||||||
--@output
|
--@output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-enum-domains:
|
-- | smb-enum-domains:
|
||||||
-- | Domain: LOCALSYSTEM
|
-- | | WINDOWS2003 (S-1-5-21-4146152237-3614947961-1862238888)
|
||||||
-- | |_ SID: S-1-5-21-2956463495-2656032972-1271678565
|
-- | | | Groups: HelpServicesGroup, IIS_WPG, TelnetClients
|
||||||
-- | |_ Users: Administrator, Guest, SUPPORT_388945a0
|
-- | | | Users: Administrator, ASPNET, Guest, IUSR_WINDOWS2003, IWAM_WINDOWS2003, ron, SUPPORT_388945a0, test
|
||||||
-- | |_ Creation time: 2007-11-26 15:24:04
|
-- | | | Creation time: 2009-10-17 12:46:43
|
||||||
-- | |_ Passwords: min length: 11 characters; min age: 5 days; max age: 63 days
|
-- | | | Passwords: min length: n/a; min age: n/a; max age: 42 days; history: n/a
|
||||||
-- | |_ Password lockout: 3 attempts in under 15 minutes will lock the account until manually reset
|
-- | | |_ Account lockout disabled
|
||||||
-- | |_ Password history : 5 passwords
|
-- | | Builtin (S-1-5-32)
|
||||||
-- | |_ Password properties:
|
-- | | | Groups: Administrators, Backup Operators, Distributed COM Users, Guests, Network Configuration Operators, Performance Log Users, Performance Monitor Users, Power Users, Print Operators, Remote Desktop Users, Replicator, Users
|
||||||
-- | |_ Password complexity requirements exist
|
-- | | | Users: n/a
|
||||||
-- | |_ Administrator account cannot be locked out
|
-- | | | Creation time: 2009-10-17 12:46:43
|
||||||
-- | Domain: Builtin
|
-- | | | Passwords: min length: n/a; min age: n/a; max age: 42 days; history: n/a
|
||||||
-- | |_ SID: S-1-5-32
|
-- |_ |_ |_ Account lockout disabled
|
||||||
-- | |_ 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
|
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -63,46 +57,191 @@ require 'msrpc'
|
|||||||
require 'smb'
|
require 'smb'
|
||||||
require 'stdnse'
|
require 'stdnse'
|
||||||
|
|
||||||
|
-- TODO: This script needs some love...
|
||||||
|
|
||||||
hostrule = function(host)
|
hostrule = function(host)
|
||||||
return smb.get_port(host) ~= nil
|
return smb.get_port(host) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function get_domain_info(smbstate, domain)
|
||||||
|
local sid
|
||||||
|
local domain_handle
|
||||||
|
|
||||||
|
-- Call LookupDomain()
|
||||||
|
status, lookupdomain_result = msrpc.samr_lookupdomain(smbstate, connect_handle, domain)
|
||||||
|
if(status == false) then
|
||||||
|
return false, "Couldn't look up the domain: " .. 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
|
||||||
|
return false, opendomain_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Save the 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
|
||||||
|
-- the most useful information.
|
||||||
|
status_1, querydomaininfo2_result_1 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 1)
|
||||||
|
status_8, querydomaininfo2_result_8 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 8)
|
||||||
|
status_12, querydomaininfo2_result_12 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 12)
|
||||||
|
|
||||||
|
if(status_1 == false) then
|
||||||
|
return false, querydomaininfo2_result_1
|
||||||
|
end
|
||||||
|
|
||||||
|
if(status_8 == false) then
|
||||||
|
return false, querydomaininfo2_result_8
|
||||||
|
end
|
||||||
|
|
||||||
|
if(status_12 == false) then
|
||||||
|
return false, thenquerydomaininfo2_result_12
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Call EnumDomainUsers() to get users
|
||||||
|
status, enumdomainusers_result = msrpc.samr_enumdomainusers(smbstate, domain_handle)
|
||||||
|
if(status == false) then
|
||||||
|
return false, enumdomainusers_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Call EnumDomainAliases() to get groups
|
||||||
|
local status, enumdomaingroups_result = msrpc.samr_enumdomainaliases(smbstate, domain_handle)
|
||||||
|
if(status == false) then
|
||||||
|
return false, enumdomaingroups_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Close the domain handle
|
||||||
|
msrpc.samr_close(smbstate, domain_handle)
|
||||||
|
|
||||||
|
-- Create a list of groups
|
||||||
|
local groups = {}
|
||||||
|
if(enumdomaingroups_result['sam'] ~= nil and enumdomaingroups_result['sam']['entries'] ~= nil) then
|
||||||
|
for _, group in ipairs(enumdomaingroups_result['sam']['entries']) do
|
||||||
|
table.insert(groups, group.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create the list of users
|
||||||
|
local names = {}
|
||||||
|
if(enumdomainusers_result['sam'] ~= nil and enumdomainusers_result['sam']['entries'] ~= nil) then
|
||||||
|
for _, name in ipairs(enumdomainusers_result['sam']['entries']) do
|
||||||
|
table.insert(names, name.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Our output table
|
||||||
|
local response = {}
|
||||||
|
|
||||||
|
-- Finally, start filling in the response!
|
||||||
|
response['name'] = string.format("%s (%s)", domain, sid)
|
||||||
|
|
||||||
|
-- Add the list of groups as a comma-separated list
|
||||||
|
if(groups and (#groups > 0)) then
|
||||||
|
table.insert(response, string.format("Groups: %s", stdnse.strjoin(", ", groups)))
|
||||||
|
else
|
||||||
|
table.insert(response, string.format("Groups: n/a"))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add the list of users as a comma-separated list
|
||||||
|
if(names and (#names > 0)) then
|
||||||
|
table.insert(response, string.format("Users: %s", stdnse.strjoin(", ", names)))
|
||||||
|
else
|
||||||
|
table.insert(response, string.format("Users: n/a"))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if(querydomaininfo2_result_8['info']['domain_create_time'] ~= 0) then
|
||||||
|
table.insert(response, string.format("Creation time: %s", os.date("%Y-%m-%d %H:%M:%S", querydomaininfo2_result_8['info']['domain_create_time'])))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Password characteristics
|
||||||
|
local min_password_length = querydomaininfo2_result_1['info']['min_password_length']
|
||||||
|
local max_password_age = querydomaininfo2_result_1['info']['max_password_age'] / 60 / 60 / 24
|
||||||
|
local min_password_age = querydomaininfo2_result_1['info']['min_password_age'] / 60 / 60 / 24
|
||||||
|
local password_history = querydomaininfo2_result_1['info']['password_history_length']
|
||||||
|
|
||||||
|
if(min_password_length > 0) then
|
||||||
|
min_password_length = string.format("%d characters", min_password_length)
|
||||||
|
else
|
||||||
|
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
|
||||||
|
|
||||||
|
if(password_history > 0) then
|
||||||
|
password_history = string.format("%d passwords", password_history)
|
||||||
|
else
|
||||||
|
password_history = "n/a"
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(response, string.format("Passwords: min length: %s; min age: %s; max age: %s; history: %s", min_password_length, min_password_age, max_password_age, password_history))
|
||||||
|
|
||||||
|
local lockout_duration = querydomaininfo2_result_12['info']['lockout_duration']
|
||||||
|
if(lockout_duration < 0) then
|
||||||
|
lockout_duration = string.format("for %d minutes", querydomaininfo2_result_12['info']['lockout_duration'])
|
||||||
|
else
|
||||||
|
lockout_duration = "until manually reset"
|
||||||
|
end
|
||||||
|
|
||||||
|
if(querydomaininfo2_result_12['info']['lockout_threshold'] > 0) then
|
||||||
|
table.insert(response, string.format("Password lockout: %d attempts in under %d minutes will lock the account %s", querydomaininfo2_result_12['info']['lockout_threshold'], querydomaininfo2_result_12['info']['lockout_window'] / 60, lockout_duration))
|
||||||
|
else
|
||||||
|
table.insert(response, string.format("Account lockout disabled"))
|
||||||
|
end
|
||||||
|
|
||||||
|
local password_properties = querydomaininfo2_result_1['info']['password_properties']
|
||||||
|
|
||||||
|
if(#password_properties > 0) then
|
||||||
|
local password_properties_response = {}
|
||||||
|
password_properties_response['name'] = "Password properties:"
|
||||||
|
for j = 1, #password_properties, 1 do
|
||||||
|
table.insert(password_properties_response, msrpc.samr_PasswordProperties_tostr(password_properties[j]))
|
||||||
|
end
|
||||||
|
table.insert(response, password_properties_response)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, response
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
action = function(host)
|
action = function(host)
|
||||||
|
|
||||||
local response = " \n"
|
local response = {}
|
||||||
local status, smbstate
|
local status, smbstate
|
||||||
local i, j
|
local i, j
|
||||||
|
|
||||||
-- Create the SMB session
|
-- Create the SMB session
|
||||||
status, smbstate = msrpc.start_smb(host, msrpc.SAMR_PATH)
|
status, smbstate = msrpc.start_smb(host, msrpc.SAMR_PATH)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, smbstate)
|
||||||
return "ERROR: " .. smbstate
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Bind to SAMR service
|
-- Bind to SAMR service
|
||||||
status, bind_result = msrpc.bind(smbstate, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil)
|
status, bind_result = msrpc.bind(smbstate, msrpc.SAMR_UUID, msrpc.SAMR_VERSION, nil)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
msrpc.stop_smb(smbstate)
|
return stdnse.format_output(false, 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(smbstate, host.ip)
|
status, connect4_result = msrpc.samr_connect4(smbstate, host.ip)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
msrpc.stop_smb(smbstate)
|
return stdnse.format_output(false, 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
|
||||||
@@ -111,170 +250,27 @@ action = function(host)
|
|||||||
-- Call EnumDomains()
|
-- Call EnumDomains()
|
||||||
status, enumdomains_result = msrpc.samr_enumdomains(smbstate, connect_handle)
|
status, enumdomains_result = msrpc.samr_enumdomains(smbstate, connect_handle)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
msrpc.stop_smb(smbstate)
|
return stdnse.format_output(false, enumdomains_result)
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return "ERROR: " .. enumdomains_result
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If no domains were returned, print an error (I don't expect this will actually happen)
|
-- If no domains were returned, print an error (I don't expect this will actually happen)
|
||||||
if(#enumdomains_result['sam']['entries'] == 0) then
|
if(#enumdomains_result['sam']['entries'] == 0) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, "Couldn't find any domains")
|
||||||
return "ERROR: Couldn't find any domains to check"
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, #enumdomains_result['sam']['entries'], 1 do
|
for i = 1, #enumdomains_result['sam']['entries'], 1 do
|
||||||
|
|
||||||
local domain = enumdomains_result['sam']['entries'][i]['name']
|
local domain = enumdomains_result['sam']['entries'][i]['name']
|
||||||
local sid
|
local status, domain_info = get_domain_info(smbstate, domain)
|
||||||
local domain_handle
|
|
||||||
|
|
||||||
-- Call LookupDomain()
|
if(not(status)) then
|
||||||
status, lookupdomain_result = msrpc.samr_lookupdomain(smbstate, connect_handle, domain)
|
local error_table = {}
|
||||||
if(status == false) then
|
error_table['name'] = "Domain: " .. domain
|
||||||
msrpc.stop_smb(smbstate)
|
error_table['warning'] = "Couldn't get info for the domain: " .. domain_info
|
||||||
if(nmap.debugging() > 0) then
|
table.insert(response, error_table)
|
||||||
return "ERROR: " .. lookupdomain_result
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
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)
|
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return "ERROR: " .. opendomain_result
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Save the 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
|
|
||||||
-- the most useful information.
|
|
||||||
status_1, querydomaininfo2_result_1 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 1)
|
|
||||||
status_8, querydomaininfo2_result_8 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 8)
|
|
||||||
status_12, querydomaininfo2_result_12 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 12)
|
|
||||||
|
|
||||||
if(status_1 == false) then
|
|
||||||
msrpc.stop_smb(smbstate)
|
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return "ERROR: " .. querydomaininfo2_result_1
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if(status_8 == false) then
|
|
||||||
msrpc.stop_smb(smbstate)
|
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return "ERROR: " .. querydomaininfo2_result_8
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if(status_12 == false) then
|
|
||||||
msrpc.stop_smb(smbstate)
|
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return "ERROR: " .. querydomaininfo2_result_12
|
|
||||||
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 and enumdomainusers_result ~= nil) then
|
|
||||||
return "ERROR: " .. enumdomainusers_result
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Close the domain handle
|
|
||||||
msrpc.samr_close(smbstate, domain_handle)
|
|
||||||
|
|
||||||
-- Create the list of users
|
|
||||||
local names = {}
|
|
||||||
if(enumdomainusers_result['sam'] ~= nil and enumdomainusers_result['sam']['entries'] ~= nil) then
|
|
||||||
for j = 1, #enumdomainusers_result['sam']['entries'], 1 do
|
|
||||||
local name = enumdomainusers_result['sam']['entries'][j]['name']
|
|
||||||
names[#names + 1] = name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Finally, fill in the response!
|
|
||||||
response = response .. string.format("Domain: %s\n", domain)
|
|
||||||
response = response .. string.format(" |_ SID: %s\n", lookupdomain_result['sid'])
|
|
||||||
if(#names ~= 0) then
|
|
||||||
response = response .. string.format(" |_ Users: %s\n", stdnse.strjoin(", ", names))
|
|
||||||
end
|
|
||||||
|
|
||||||
if(querydomaininfo2_result_8['info']['domain_create_time'] ~= 0) then
|
|
||||||
response = response .. string.format(" |_ Creation time: %s\n", os.date("%Y-%m-%d %H:%M:%S", querydomaininfo2_result_8['info']['domain_create_time']))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Password characteristics
|
|
||||||
local min_password_length = querydomaininfo2_result_1['info']['min_password_length']
|
|
||||||
local max_password_age = querydomaininfo2_result_1['info']['max_password_age'] / 60 / 60 / 24
|
|
||||||
local min_password_age = querydomaininfo2_result_1['info']['min_password_age'] / 60 / 60 / 24
|
|
||||||
|
|
||||||
if(min_password_length > 0) then
|
|
||||||
min_password_length = string.format("%d characters", min_password_length)
|
|
||||||
else
|
else
|
||||||
min_password_length = "n/a"
|
table.insert(response, domain_info)
|
||||||
end
|
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_12['info']['lockout_duration']
|
|
||||||
if(lockout_duration < 0) then
|
|
||||||
lockout_duration = string.format("for %d minutes", querydomaininfo2_result_12['info']['lockout_duration'])
|
|
||||||
else
|
|
||||||
lockout_duration = "until manually reset"
|
|
||||||
end
|
|
||||||
|
|
||||||
if(querydomaininfo2_result_12['info']['lockout_threshold'] > 0) then
|
|
||||||
response = response .. string.format(" |_ Password lockout: %d attempts in under %d minutes will lock the account %s\n", querydomaininfo2_result_12['info']['lockout_threshold'], querydomaininfo2_result_12['info']['lockout_window'] / 60, lockout_duration)
|
|
||||||
else
|
|
||||||
response = response .. string.format(" |_ Account lockout disabled\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
if(querydomaininfo2_result_1['info']['password_history_length']) > 0 then
|
|
||||||
response = response .. string.format(" |_ Password history: %d passwords\n", querydomaininfo2_result_1['info']['password_history_length'])
|
|
||||||
end
|
|
||||||
|
|
||||||
local password_properties = querydomaininfo2_result_1['info']['password_properties']
|
|
||||||
if(#password_properties > 0) then
|
|
||||||
response = response .. " |_ Password properties:\n"
|
|
||||||
for j = 1, #password_properties, 1 do
|
|
||||||
response = response .. " |_ " .. msrpc.samr_PasswordProperties_tostr(password_properties[j]) .. "\n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Close the connect handle
|
-- Close the connect handle
|
||||||
@@ -283,8 +279,6 @@ action = function(host)
|
|||||||
-- Close the SMB session
|
-- Close the SMB session
|
||||||
msrpc.stop_smb(smbstate)
|
msrpc.stop_smb(smbstate)
|
||||||
|
|
||||||
return response
|
return stdnse.format_output(true, response)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
92
scripts/smb-enum-groups.nse
Normal file
92
scripts/smb-enum-groups.nse
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
description = [[
|
||||||
|
Obtains a list of groups from the remote Windows system, as well as a list of the group's users.
|
||||||
|
This works similarly to enum.exe with the /G switch.
|
||||||
|
|
||||||
|
The following MSRPC functions in SAMR are used to find a list of groups and the RIDs of their users. Keep
|
||||||
|
in mind thatMSRPC refers to groups as 'Aliases'.
|
||||||
|
|
||||||
|
* <code>Bind</code>: bind to the SAMR service.
|
||||||
|
* <code>Connect4</code>: get a connect_handle.
|
||||||
|
* <code>EnumDomains</code>: get a list of the domains.
|
||||||
|
* <code>LookupDomain</code>: get the RID of the domains.
|
||||||
|
* <code>OpenDomain</code>: get a handle for each domain.
|
||||||
|
* <code>EnumDomainAliases</code>: get the list of groups in the domain.
|
||||||
|
* <code>OpenAlias</code>: get a handle to each group.
|
||||||
|
* <code>GetMembersInAlias</code>: get the RIDs of the members in the groups.
|
||||||
|
* <code>Close</code>: close the alias handle.
|
||||||
|
* <code>Close</code>: close the domain handle.
|
||||||
|
* <code>Close</code>: close the connect handle.
|
||||||
|
|
||||||
|
Once the RIDs have been termined, the
|
||||||
|
* <code>Bind</code>: bind to the LSA service.
|
||||||
|
* <code>OpenPolicy2</code>: get a policy handle.
|
||||||
|
* <code>LookupSids2</code>: convert SIDs to usernames.
|
||||||
|
|
||||||
|
I (Ron Bowes) originally looked into the possibility of using the SAMR function <code>LookupRids2</code>
|
||||||
|
to convert RIDs to usernames, but the function seemed to return a fault no matter what I tried. Since
|
||||||
|
enum.exe also switches to LSA to convert RIDs to usernames, I figured they had the same issue and I do
|
||||||
|
the same thing.
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap --script smb-enum-users.nse -p445 <host>
|
||||||
|
-- sudo nmap -sU -sS --script smb-enum-users.nse -p U:137,T:139 <host>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- Host script results:
|
||||||
|
-- | smb-enum-groups:
|
||||||
|
-- | | WINDOWS2003\HelpServicesGroup: SUPPORT_388945a0
|
||||||
|
-- | | WINDOWS2003\IIS_WPG: SYSTEM, SERVICE, NETWORK SERVICE, IWAM_WINDOWS2003
|
||||||
|
-- | | WINDOWS2003\TelnetClients: <empty>
|
||||||
|
-- | | Builtin\Print Operators: <empty>
|
||||||
|
-- | | Builtin\Replicator: <empty>
|
||||||
|
-- | | Builtin\Network Configuration Operators: <empty>
|
||||||
|
-- | | Builtin\Performance Monitor Users: <empty>
|
||||||
|
-- | | Builtin\Users: INTERACTIVE, Authenticated Users, ron, ASPNET, test
|
||||||
|
-- | | Builtin\Power Users: <empty>
|
||||||
|
-- | | Builtin\Backup Operators: <empty>
|
||||||
|
-- | | Builtin\Remote Desktop Users: <empty>
|
||||||
|
-- | | Builtin\Administrators: Administrator, ron, test
|
||||||
|
-- | | Builtin\Performance Log Users: NETWORK SERVICE
|
||||||
|
-- | | Builtin\Guests: Guest, IUSR_WINDOWS2003
|
||||||
|
-- |_ |_ Builtin\Distributed COM Users: <empty>
|
||||||
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
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)
|
||||||
|
return smb.get_port(host) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
action = function(host)
|
||||||
|
local status, groups = msrpc.samr_enum_groups(host)
|
||||||
|
if(not(status)) then
|
||||||
|
return stdnse.format_output(false, "Couldn't enumerate groups: " .. groups)
|
||||||
|
end
|
||||||
|
|
||||||
|
local response = {}
|
||||||
|
|
||||||
|
for domain_name, domain_data in pairs(groups) do
|
||||||
|
|
||||||
|
for rid, group_data in pairs(domain_data) do
|
||||||
|
local members = group_data['members']
|
||||||
|
if(#members > 0) then
|
||||||
|
members = stdnse.strjoin(", ", group_data['members'])
|
||||||
|
else
|
||||||
|
members = "<empty>"
|
||||||
|
end
|
||||||
|
table.insert(response, string.format("%s\\%s (RID: %s): %s", domain_name, group_data['name'], rid, members))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return stdnse.format_output(true, response)
|
||||||
|
end
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ impact the system, besides showing a message box to the user.
|
|||||||
-- @output
|
-- @output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-enum-processes:
|
-- | smb-enum-processes:
|
||||||
-- |_ Idle, System, smss, csrss, winlogon, services, logon.scr, lsass, spoolsv, msdtc, VMwareService, svchost, alg, explorer, VMwareTray, VMwareUser, wmiprvse
|
-- |_ |_ Idle, System, smss, csrss, winlogon, services, logon.scr, lsass, spoolsv, msdtc, VMwareService, svchost, alg, explorer, VMwareTray, VMwareUser, wmiprvse
|
||||||
--
|
--
|
||||||
-- --
|
-- --
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
@@ -281,7 +281,7 @@ action = function(host)
|
|||||||
|
|
||||||
-- Produce final output.
|
-- Produce final output.
|
||||||
if nmap.verbosity() == 0 then
|
if nmap.verbosity() == 0 then
|
||||||
response = stdnse.strjoin(", ", names)
|
response = "|_ " .. stdnse.strjoin(", ", names)
|
||||||
else
|
else
|
||||||
response = " \n" .. psl_print(psl, nmap.verbosity())
|
response = " \n" .. psl_print(psl, nmap.verbosity())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ the system, besides showing a message box to the user.
|
|||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-enum-sessions:
|
-- | smb-enum-sessions:
|
||||||
-- | Users logged in:
|
-- | Users logged in:
|
||||||
-- | |_ TESTBOX\Administrator since 2008-10-21 08:17:14
|
-- | | TESTBOX\Administrator since 2008-10-21 08:17:14
|
||||||
-- | |_ DOMAIN\rbowes since 2008-10-20 09:03:23
|
-- | |_ DOMAIN\rbowes since 2008-10-20 09:03:23
|
||||||
-- | Active SMB Sessions:
|
-- | Active SMB Sessions:
|
||||||
-- |_ |_ ADMINISTRATOR is connected from 10.100.254.138 for [just logged in, it's probably you], idle for [not idle]
|
-- |_ |_ ADMINISTRATOR is connected from 10.100.254.138 for [just logged in, it's probably you], idle for [not idle]
|
||||||
@@ -264,39 +264,41 @@ end
|
|||||||
action = function(host)
|
action = function(host)
|
||||||
-- TRACEBACK[coroutine.running()] = true;
|
-- TRACEBACK[coroutine.running()] = true;
|
||||||
|
|
||||||
local response = " \n"
|
local response = {}
|
||||||
|
|
||||||
local status1, status2
|
local status1, status2
|
||||||
|
|
||||||
-- Enumerate the logged in users
|
-- Enumerate the logged in users
|
||||||
|
local logged_in = {}
|
||||||
|
logged_in['name'] = "Users logged in"
|
||||||
status1, users = winreg_enum_rids(host)
|
status1, users = winreg_enum_rids(host)
|
||||||
if(status1 == false) then
|
if(status1 == false) then
|
||||||
response = response .. "ERROR: Couldn't enumerate login sessions: " .. users .. "\n"
|
logged_in['warning'] = "Couldn't enumerate login sessions: " .. users
|
||||||
else
|
else
|
||||||
response = response .. "Users logged in:\n"
|
|
||||||
if(#users == 0) then
|
if(#users == 0) then
|
||||||
response = response .. "|_ <nobody>\n"
|
table.insrt(response, "<nobody>")
|
||||||
else
|
else
|
||||||
for i = 1, #users, 1 do
|
for i = 1, #users, 1 do
|
||||||
if(users[i]['name'] ~= nil) then
|
if(users[i]['name'] ~= nil) then
|
||||||
response = response .. string.format("|_ %s\\%s since %s\n", users[i]['domain'], users[i]['name'], users[i]['changed_date'])
|
table.insert(logged_in, string.format("%s\\%s since %s", users[i]['domain'], users[i]['name'], users[i]['changed_date']))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
table.insert(response, logged_in)
|
||||||
|
|
||||||
-- Get the connected sessions
|
-- Get the connected sessions
|
||||||
|
local sessions_output = {}
|
||||||
|
sessions_output['name'] = "Active SMB sessions"
|
||||||
status2, sessions = srvsvc_enum_sessions(host)
|
status2, sessions = srvsvc_enum_sessions(host)
|
||||||
if(status2 == false) then
|
if(status2 == false) then
|
||||||
response = response .. "ERROR: Couldn't enumerate network sessions: " .. sessions .. "\n"
|
sessions['warning'] = "Couldn't enumerate network sessions: " .. sessions
|
||||||
else
|
else
|
||||||
response = response .. "Active SMB Sessions:\n"
|
|
||||||
if(#sessions == 0) then
|
if(#sessions == 0) then
|
||||||
response = response .. "|_ <none>\n"
|
table.insert(sessions_output, "<none>")
|
||||||
else
|
else
|
||||||
-- Format the result
|
-- Format the result
|
||||||
for i = 1, #sessions, 1 do
|
for i = 1, #sessions, 1 do
|
||||||
|
|
||||||
local time = sessions[i]['time']
|
local time = sessions[i]['time']
|
||||||
if(time == 0) then
|
if(time == 0) then
|
||||||
time = "[just logged in, it's probably you]"
|
time = "[just logged in, it's probably you]"
|
||||||
@@ -318,21 +320,14 @@ action = function(host)
|
|||||||
else
|
else
|
||||||
idle_time = string.format("%02dm%02ds", idle_time / 60, idle_time % 60)
|
idle_time = string.format("%02dm%02ds", idle_time / 60, idle_time % 60)
|
||||||
end
|
end
|
||||||
|
|
||||||
response = response .. string.format("|_ %s is connected from %s for %s, idle for %s\n", sessions[i]['user'], sessions[i]['client'], time, idle_time)
|
table.insert(sessions_output, string.format("%s is connected from %s for %s, idle for %s", sessions[i]['user'], sessions[i]['client'], time, idle_time))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
table.insert(response, sessions_output)
|
||||||
|
|
||||||
if(status1 == false and status2 == false) then
|
return stdnse.format_output(true, response)
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return response
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,36 +29,28 @@ for shares that require a user account.
|
|||||||
--
|
--
|
||||||
--@output
|
--@output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-enum-shares:
|
-- | smb-enum-shares:
|
||||||
-- | ADMIN$
|
-- | | ADMIN$
|
||||||
-- | |_ Type: STYPE_DISKTREE_HIDDEN
|
-- | | | Type: STYPE_DISKTREE_HIDDEN
|
||||||
-- | |_ Comment: Remote Admin
|
-- | | | Comment: Remote Admin
|
||||||
-- | |_ Users: 0, Max: <unlimited>
|
-- | | | Users: 0, Max: <unlimited>
|
||||||
-- | |_ Path: C:\WINNT
|
-- | | | Path: C:\WINNT
|
||||||
-- | |_ Anonymous access: <none>
|
-- | | | Anonymous access: <none>
|
||||||
-- | |_ Current user ('test') access: READ/WRITE
|
-- | | |_ Current user ('administrator') access: READ/WRITE
|
||||||
-- | C$
|
-- | | C$
|
||||||
-- | |_ Type: STYPE_DISKTREE_HIDDEN
|
-- | | | Type: STYPE_DISKTREE_HIDDEN
|
||||||
-- | |_ Comment: Default share
|
-- | | | Comment: Default share
|
||||||
-- | |_ Users: 0, Max: <unlimited>
|
-- | | | Users: 0, Max: <unlimited>
|
||||||
-- | |_ Path: C:\
|
-- | | | Path: C:\
|
||||||
-- | |_ Anonymous access: <none>
|
-- | | | Anonymous access: <none>
|
||||||
-- | |_ Current user ('test') access: READ
|
-- | | |_ Current user ('administrator') access: READ
|
||||||
-- | IPC$
|
-- | | IPC$
|
||||||
-- | |_ Type: STYPE_IPC_HIDDEN
|
-- | | | Type: STYPE_IPC_HIDDEN
|
||||||
-- | |_ Comment: Remote IPC
|
-- | | | Comment: Remote IPC
|
||||||
-- | |_ Users: 1, Max: <unlimited>
|
-- | | | Users: 1, Max: <unlimited>
|
||||||
-- | |_ Path:
|
-- | | | Path:
|
||||||
-- | |_ Anonymous access: READ <not a file share>
|
-- | | | Anonymous access: READ <not a file share>
|
||||||
-- | |_ Current user ('test') access: READ <not a file share>
|
-- |_ |_ |_ Current user ('administrator') access: READ <not a file share>
|
||||||
-- | 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
|
|
||||||
-- | |_ Anonymous access: <none>
|
|
||||||
-- |_ |_ Current user ('test') access: READ/WRITE
|
|
||||||
|
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -74,14 +66,14 @@ hostrule = function(host)
|
|||||||
return smb.get_port(host) ~= nil
|
return smb.get_port(host) ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function go(host)
|
action = function(host)
|
||||||
local status, shares, extra
|
local status, shares, extra
|
||||||
local response = " \n"
|
local response = {}
|
||||||
|
|
||||||
-- Get the list of shares
|
-- Get the list of shares
|
||||||
status, shares, extra = smb.share_get_list(host)
|
status, shares, extra = smb.share_get_list(host)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
return false, string.format("Couldn't enumerate shares: %s", shares)
|
return stdnse.format_output(false, string.format("Couldn't enumerate shares: %s", shares))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Find out who the current user is
|
-- Find out who the current user is
|
||||||
@@ -91,25 +83,24 @@ local function go(host)
|
|||||||
domain = ""
|
domain = ""
|
||||||
end
|
end
|
||||||
|
|
||||||
if(extra ~= nil) then
|
if(extra ~= nil and extra ~= '') then
|
||||||
response = response .. extra .. "\n"
|
table.insert(response, extra)
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, #shares, 1 do
|
for i = 1, #shares, 1 do
|
||||||
local share = shares[i]
|
local share = shares[i]
|
||||||
|
local share_output = {}
|
||||||
|
share_output['name'] = share['name']
|
||||||
|
|
||||||
-- Start generating a human-readable string
|
|
||||||
response = response .. share['name'] .. "\n"
|
|
||||||
|
|
||||||
if(type(share['details']) ~= 'table') then
|
if(type(share['details']) ~= 'table') then
|
||||||
response = response .. string.format("|_ Couldn't get details for share: %s\n", share['details'])
|
share_output['warning'] = string.format("Couldn't get details for share: %s", share['details'])
|
||||||
else
|
else
|
||||||
local details = share['details']
|
local details = share['details']
|
||||||
|
|
||||||
response = response .. string.format("|_ Type: %s\n", details['sharetype'])
|
table.insert(share_output, string.format("Type: %s", details['sharetype']))
|
||||||
response = response .. string.format("|_ Comment: %s\n", details['comment'])
|
table.insert(share_output, string.format("Comment: %s", details['comment']))
|
||||||
response = response .. string.format("|_ Users: %s, Max: %s\n", details['current_users'], details['max_users'])
|
table.insert(share_output, string.format("Users: %s, Max: %s", details['current_users'], details['max_users']))
|
||||||
response = response .. string.format("|_ Path: %s\n", details['path'])
|
table.insert(share_output, string.format("Path: %s", details['path']))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -117,64 +108,47 @@ local function go(host)
|
|||||||
if(share['user_can_write'] == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then
|
if(share['user_can_write'] == "NT_STATUS_OBJECT_NAME_NOT_FOUND") then
|
||||||
-- Print details for a non-file share
|
-- Print details for a non-file share
|
||||||
if(share['anonymous_can_read']) then
|
if(share['anonymous_can_read']) then
|
||||||
response = response .. "|_ Anonymous access: READ <not a file share>\n"
|
table.insert(share_output, "Anonymous access: READ <not a file share>")
|
||||||
else
|
else
|
||||||
response = response .. "|_ Anonymous access: <none> <not a file share>\n"
|
table.insert(share_output, "Anonymous access: <none> <not a file share>")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Don't bother printing this if we're already anonymous
|
-- Don't bother printing this if we're already anonymous
|
||||||
if(username ~= '') then
|
if(username ~= '') then
|
||||||
if(share['user_can_read']) then
|
if(share['user_can_read']) then
|
||||||
response = response .. "|_ Current user ('" .. username .. "') access: READ <not a file share>\n"
|
table.insert(share_output, "Current user ('" .. username .. "') access: READ <not a file share>")
|
||||||
else
|
else
|
||||||
response = response .. "|_ Current user ('" .. username .. "') access: <none> <not a file share>\n"
|
table.insert(share_output, "Current user ('" .. username .. "') access: <none> <not a file share>")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Print details for a file share
|
-- Print details for a file share
|
||||||
if(share['anonymous_can_read'] and share['anonymous_can_write']) then
|
if(share['anonymous_can_read'] and share['anonymous_can_write']) then
|
||||||
response = response .. "|_ Anonymous access: READ/WRITE\n"
|
table.insert(share_output, "Anonymous access: READ/WRITE")
|
||||||
elseif(share['anonymous_can_read'] and not(share['anonymous_can_write'])) then
|
elseif(share['anonymous_can_read'] and not(share['anonymous_can_write'])) then
|
||||||
response = response .. "|_ Anonymous access: READ\n"
|
table.insert(share_output, "Anonymous access: READ")
|
||||||
elseif(not(share['anonymous_can_read']) and share['anonymous_can_write']) then
|
elseif(not(share['anonymous_can_read']) and share['anonymous_can_write']) then
|
||||||
response = response .. "|_ Anonymous access: WRITE\n"
|
table.insert(share_output, "Anonymous access: WRITE")
|
||||||
else
|
else
|
||||||
response = response .. "|_ Anonymous access: <none>\n"
|
table.insert(share_output, "Anonymous access: <none>")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(username ~= '') then
|
if(username ~= '') then
|
||||||
if(share['user_can_read'] and share['user_can_write']) then
|
if(share['user_can_read'] and share['user_can_write']) then
|
||||||
response = response .. "|_ Current user ('" .. username .. "') access: READ/WRITE\n"
|
table.insert(share_output, "Current user ('" .. username .. "') access: READ/WRITE")
|
||||||
elseif(share['user_can_read'] and not(share['user_can_write'])) then
|
elseif(share['user_can_read'] and not(share['user_can_write'])) then
|
||||||
response = response .. "|_ Current user ('" .. username .. "') access: READ\n"
|
table.insert(share_output, "Current user ('" .. username .. "') access: READ")
|
||||||
elseif(not(share['user_can_read']) and share['user_can_write']) then
|
elseif(not(share['user_can_read']) and share['user_can_write']) then
|
||||||
response = response .. "|_ Current user ('" .. username .. "') access: WRITE\n"
|
table.insert(share_output, "Current user ('" .. username .. "') access: WRITE")
|
||||||
else
|
else
|
||||||
response = response .. "|_ Current user ('" .. username .. "') access: <none>\n"
|
table.insert(share_output, "Current user ('" .. username .. "') access: <none>")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
table.insert(response, share_output)
|
||||||
end
|
end
|
||||||
|
|
||||||
return true, response
|
return stdnse.format_output(true, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
action = function(host)
|
|
||||||
local status, result
|
|
||||||
|
|
||||||
status, result = go(host)
|
|
||||||
|
|
||||||
if(status == false) then
|
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return "ERROR: " .. result
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -99,42 +99,30 @@ the code I wrote for this is largely based on the techniques used by them.
|
|||||||
-- @output
|
-- @output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-enum-users:
|
-- | smb-enum-users:
|
||||||
-- |_ TESTBOX\Administrator, EXTERNAL\DnsAdmins, TESTBOX\Guest,
|
-- |_ |_ Domain: RON-WIN2K-TEST; Users: Administrator, Guest, IUSR_RON-WIN2K-TEST, IWAM_RON-WIN2K-TEST, test1234, TsInternetUser
|
||||||
-- EXTERNAL\HelpServicesGroup, EXTERNAL\PARTNERS$, TESTBOX\SUPPORT_388945a0
|
|
||||||
--
|
--
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-enum-users:
|
-- | smb-enum-users:
|
||||||
-- | Administrator
|
-- | | RON-WIN2K-TEST\Administrator (RID: 500)
|
||||||
-- | |_ Type: User
|
-- | | | Description: Built-in account for administering the computer/domain
|
||||||
-- | |_ Domain: LOCALSYSTEM
|
-- | | |_ Flags: Password does not expire, Normal user account
|
||||||
-- | |_ Full name: Built-in account for administering the computer/domain
|
-- | | RON-WIN2K-TEST\Guest (RID: 501)
|
||||||
-- | |_ Flags: Normal account, Password doesn't expire
|
-- | | | Description: Built-in account for guest access to the computer/domain
|
||||||
-- | DnsAdmins
|
-- | | |_ Flags: Password not required, Password does not expire, Normal user account
|
||||||
-- | |_ Type: Alias
|
-- | | RON-WIN2K-TEST\IUSR_RON-WIN2K-TEST (RID: 1001)
|
||||||
-- | |_ Domain: EXTRANET
|
-- | | | Full name: Internet Guest Account
|
||||||
-- | EventViewer
|
-- | | | Description: Built-in account for anonymous access to Internet Information Services
|
||||||
-- | |_ Type: User
|
-- | | |_ Flags: Password not required, Password does not expire, Normal user account
|
||||||
-- | |_ Domain: SHARED
|
-- | | RON-WIN2K-TEST\IWAM_RON-WIN2K-TEST (RID: 1002)
|
||||||
-- | ProxyUsers
|
-- | | | Full name: Launch IIS Process Account
|
||||||
-- | |_ Type: Group
|
-- | | | Description: Built-in account for Internet Information Services to start out of process applications
|
||||||
-- | |_ Domain: EXTRANET
|
-- | | |_ Flags: Password not required, Password does not expire, Normal user account
|
||||||
-- | ComputerAccounts
|
-- | | RON-WIN2K-TEST\test1234 (RID: 1005)
|
||||||
-- | |_ Type: Group
|
-- | | |_ Flags: Normal user account
|
||||||
-- | |_ Domain: EXTRANET
|
-- | | RON-WIN2K-TEST\TsInternetUser (RID: 1000)
|
||||||
-- | Helpdesk
|
-- | | | Full name: TsInternetUser
|
||||||
-- | |_ Type: Group
|
-- | | | Description: This user account is used by Terminal Services.
|
||||||
-- | |_ Domain: EXTRANET
|
-- |_ |_ |_ Flags: Password not required, Password does not expire, Normal user account
|
||||||
-- | 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 lsaonly If set, script will only enumerate using an LSA bruteforce (requires less
|
-- @args lsaonly If set, script will only enumerate using an LSA bruteforce (requires less
|
||||||
-- access than samr). Only set if you know what you're doing, you'll get better results
|
-- access than samr). Only set if you know what you're doing, you'll get better results
|
||||||
@@ -165,112 +153,109 @@ action = function(host)
|
|||||||
local samr_result = "Didn't run"
|
local samr_result = "Didn't run"
|
||||||
local lsa_result = "Didn't run"
|
local lsa_result = "Didn't run"
|
||||||
local names = {}
|
local names = {}
|
||||||
local name_strings = {}
|
local names_lookup = {}
|
||||||
local response = " \n"
|
local response = {}
|
||||||
local samronly = nmap.registry.args.samronly
|
local samronly = nmap.registry.args.samronly
|
||||||
local lsaonly = nmap.registry.args.lsaonly
|
local lsaonly = nmap.registry.args.lsaonly
|
||||||
local do_samr = samronly ~= nil or (samronly == nil and lsaonly == nil)
|
local do_samr = samronly ~= nil or (samronly == nil and lsaonly == nil)
|
||||||
local do_lsa = lsaonly ~= nil or (samronly == nil and lsaonly == nil)
|
local do_lsa = lsaonly ~= nil or (samronly == nil and lsaonly == nil)
|
||||||
|
|
||||||
-- Try enumerating through LSA first. Since LSA provides less information, we want the
|
-- Try enumerating through SAMR. This is the better source of information, if we can get it.
|
||||||
-- SAMR result to overwrite it.
|
if(do_samr) then
|
||||||
if(do_lsa) then
|
samr_status, samr_result = msrpc.samr_enum_users(host)
|
||||||
lsa_status, lsa_result = msrpc.lsa_enum_users(host)
|
|
||||||
if(lsa_status == false) then
|
if(samr_status) then
|
||||||
if(nmap.debugging() > 0) then
|
-- Copy the returned array into the names[] table
|
||||||
response = response .. "ERROR: Couldn't enumerate through LSA: " .. lsa_result .. "\n"
|
stdnse.print_debug(2, "EnumUsers: Received %d names from SAMR", #samr_result)
|
||||||
end
|
for i = 1, #samr_result, 1 do
|
||||||
else
|
-- Insert the full info into the names list
|
||||||
-- Copy the returned array into the names[] table, using the name as the key
|
table.insert(names, samr_result[i])
|
||||||
stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
|
-- Set the names_lookup value to 'true' to avoid duplicates
|
||||||
for i = 1, #lsa_result, 1 do
|
names_lookup[samr_result[i]['name']] = true
|
||||||
if(lsa_result[i]['name'] ~= nil) then
|
|
||||||
names[string.upper(lsa_result[i]['name'])] = lsa_result[i]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Try enumerating through SAMR
|
-- Try enumerating through LSA.
|
||||||
if(do_samr) then
|
if(do_lsa) then
|
||||||
samr_status, samr_result = msrpc.samr_enum_users(host)
|
lsa_status, lsa_result = msrpc.lsa_enum_users(host)
|
||||||
if(samr_status == false) then
|
if(lsa_status) then
|
||||||
if(nmap.debugging() > 0) then
|
-- Copy the returned array into the names[] table
|
||||||
response = response .. "ERROR: Couldn't enumerate through SAMR: " .. samr_result .. "\n"
|
stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
|
||||||
end
|
for i = 1, #lsa_result, 1 do
|
||||||
else
|
if(lsa_result[i]['name'] ~= nil) then
|
||||||
-- Copy the returned array into the names[] table, using the name as the key
|
-- Check if the name already exists
|
||||||
stdnse.print_debug(2, "EnumUsers: Received %d names from SAMR", #samr_result)
|
if(not(names_lookup[lsa_result[i]['name']])) then
|
||||||
for i = 1, #samr_result, 1 do
|
table.insert(names, lsa_result[i])
|
||||||
names[string.upper(samr_result[i]['name'])] = samr_result[i]
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if both failed
|
-- Check if both failed
|
||||||
if(samr_status == false and lsa_status == false) then
|
if(samr_status == false and lsa_status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
if(string.find(lsa_result, 'ACCESS_DENIED')) then
|
||||||
return response
|
return stdnse.format_output(false, "Access denied while trying to enumerate users; except against Windows 2000, Guest or better is typically required")
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return stdnse.format_output(false, {"Couldn't enumerate users", "SAMR returned " .. samr_result, "LSA returned " .. lsa_result})
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Put the names into an array of strings, so we can sort them
|
|
||||||
for name, details in pairs(names) do
|
|
||||||
name_strings[#name_strings + 1] = names[name]['name']
|
|
||||||
end
|
|
||||||
-- Sort them
|
-- Sort them
|
||||||
table.sort(name_strings, function (a, b) return string.lower(a) < string.lower(b) end)
|
table.sort(names, function (a, b) return string.lower(a.name) < string.lower(b.name) end)
|
||||||
|
|
||||||
|
-- Break them out by domain
|
||||||
|
local domains = {}
|
||||||
|
for _, name in ipairs(names) do
|
||||||
|
local domain = name['domain']
|
||||||
|
|
||||||
|
-- Make sure the entry in the domains table exists
|
||||||
|
if(not(domains[domain])) then
|
||||||
|
domains[domain] = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(domains[domain], name)
|
||||||
|
end
|
||||||
|
|
||||||
-- Check if we actually got any names back
|
-- Check if we actually got any names back
|
||||||
if(#name_strings == 0) then
|
if(#names == 0) then
|
||||||
response = response .. "Couldn't find any account names anonymously, sorry!"
|
table.insert(response, "Couldn't find any account names, sorry!")
|
||||||
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
|
||||||
local response_array = {}
|
for domain, domain_users in pairs(domains) do
|
||||||
for i = 1, #name_strings, 1 do
|
-- Make an impromptu list of users
|
||||||
local name = string.upper(name_strings[i])
|
local names = {}
|
||||||
response_array[#response_array + 1] = (names[name]['domain'] .. "\\" .. names[name]['name'])
|
for _, info in ipairs(domain_users) do
|
||||||
end
|
table.insert(names, info['name'])
|
||||||
|
|
||||||
response = response .. stdnse.strjoin(", ", response_array)
|
|
||||||
else
|
|
||||||
for i = 1, #name_strings, 1 do
|
|
||||||
local name = string.upper(name_strings[i])
|
|
||||||
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(nmap.verbosity() > 1) then
|
|
||||||
if(names[name]['rid'] ~= nil) then response = response .. string.format(" |_ RID: %s\n", names[name]['rid']) end
|
|
||||||
end
|
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
|
-- Add this domain to the response
|
||||||
|
table.insert(response, string.format("Domain: %s; Users: %s", domain, stdnse.strjoin(", ", names)))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for domain, domain_users in pairs(domains) do
|
||||||
|
for _, info in ipairs(domain_users) do
|
||||||
|
local response_part = {}
|
||||||
|
response_part['name'] = string.format("%s\\%s (RID: %d)", domain, info['name'], info['rid'])
|
||||||
|
|
||||||
if(nmap.verbosity() > 1) then
|
if(info['fullname']) then
|
||||||
if(names[name]['source'] ~= nil) then response = response .. string.format(" |_ Source: %s\n", names[name]['source']) end
|
table.insert(response_part, string.format("Full name: %s", info['fullname']))
|
||||||
|
end
|
||||||
|
if(info['description']) then
|
||||||
|
table.insert(response_part, string.format("Description: %s", info['description']))
|
||||||
|
end
|
||||||
|
if(info['flags']) then
|
||||||
|
table.insert(response_part, string.format("Flags: %s", stdnse.strjoin(", ", info['flags'])))
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(response, response_part)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return response
|
return stdnse.format_output(true, 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
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,10 +27,11 @@ they likely won't change the outcome in any meaningful way.
|
|||||||
-- 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
|
||||||
-- | smb-os-discovery: Windows 2000
|
-- Host script results:
|
||||||
-- | LAN Manager: Windows 2000 LAN Manager
|
-- | smb-os-discovery:
|
||||||
-- | Name: WORKGROUP\TEST1
|
-- | | OS: Windows 2000 (Windows 2000 LAN Manager)
|
||||||
-- |_ System time: 2008-09-09 20:55:55 UTC-5
|
-- | | Name: WORKGROUP\RON-WIN2K-TEST
|
||||||
|
-- |_ |_ System time: 2009-11-09 14:33:39 UTC-6
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -61,18 +62,18 @@ function get_windows_version(os)
|
|||||||
end
|
end
|
||||||
|
|
||||||
action = function(host)
|
action = function(host)
|
||||||
|
local response = {}
|
||||||
local status, result = smb.get_os(host)
|
local status, result = smb.get_os(host)
|
||||||
|
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, result)
|
||||||
return "smb-os-discovery: ERROR: " .. result
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return string.format("%s\nLAN Manager: %s\nName: %s\\%s\nSystem time: %s %s\n", get_windows_version(result['os']), result['lanmanager'], result['domain'], result['server'], result['date'], result['timezone_str'])
|
table.insert(response, string.format("OS: %s (%s)", get_windows_version(result['os']), result['lanmanager']))
|
||||||
|
table.insert(response, string.format("Name: %s\\%s", result['domain'], result['server']))
|
||||||
|
table.insert(response, string.format("System time: %s %s", result['date'], result['timezone_str']))
|
||||||
|
|
||||||
|
return stdnse.format_output(true, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -304,32 +304,82 @@ Some ideas for later versions:
|
|||||||
--
|
--
|
||||||
-- @output
|
-- @output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-psexec:
|
-- | smb-psexec:
|
||||||
-- | IP Address and MAC Address from 'ipconfig.exe'
|
-- | | Windows version
|
||||||
-- | | Ethernet adapter Local Area Connection:
|
-- | | |_ Microsoft Windows 2000 [Version 5.00.2195]
|
||||||
-- | | MAC Address: 00:0C:29:12:E6:DB
|
-- | | IP Address and MAC Address from 'ipconfig.exe'
|
||||||
-- | |_ IP Address: 192.168.1.21
|
-- | | | Ethernet adapter Local Area Connection 2:
|
||||||
-- |
|
-- | | | MAC Address: 00:50:56:A1:24:C2
|
||||||
-- | User list from 'net user'
|
-- | | | IP Address: 10.0.0.30
|
||||||
-- | | Administrator Guest ron
|
-- | | | Ethernet adapter Local Area Connection:
|
||||||
-- | |_SUPPORT_388945a0 test
|
-- | | |_ MAC Address: 00:50:56:A1:00:65
|
||||||
-- |
|
-- | | User list from 'net user'
|
||||||
-- | Membership of 'administrators' from 'net localgroup administrators'
|
-- | | | Administrator TestUser3 Guest
|
||||||
-- | | Administrator
|
-- | | | IUSR_RON-WIN2K-TEST IWAM_RON-WIN2K-TEST nmap
|
||||||
-- | | ron
|
-- | | | rontest123 sshd SvcCOPSSH
|
||||||
-- | |_test
|
-- | | |_ test1234 Testing TsInternetUser
|
||||||
-- |
|
-- | | Membership of 'administrators' from 'net localgroup administrators'
|
||||||
-- | Can the host ping our address?
|
-- | | | Administrator
|
||||||
-- | | Pinging 192.168.1.100 with 32 bytes of data:
|
-- | | | SvcCOPSSH
|
||||||
-- | |_Reply from 192.168.1.100: bytes=32 time<1ms TTL=64
|
-- | | | test1234
|
||||||
-- |
|
-- | | |_ Testing
|
||||||
-- | Traceroute back to the scanner
|
-- | | Can the host ping our address?
|
||||||
-- | |_ 1 <1 ms <1 ms <1 ms 192.168.1.100
|
-- | | | Pinging 10.0.0.138 with 32 bytes of data:
|
||||||
-- |
|
-- | | |_ Reply from 10.0.0.138: bytes=32 time<10ms TTL=64
|
||||||
-- | ARP Cache from arp.exe
|
-- | | Traceroute back to the scanner
|
||||||
-- | | Internet Address Physical Address Type
|
-- | | |_ 1 <10 ms <10 ms <10 ms 10.0.0.138
|
||||||
-- | |_ 192.168.1.100 00-21-9b-e5-78-ea dynamic
|
-- | | ARP Cache from arp.exe
|
||||||
-- |_
|
-- | | | Internet Address Physical Address Type
|
||||||
|
-- | | |_ 10.0.0.138 00-50-56-a1-27-4b dynamic
|
||||||
|
-- | | List of listening and established connections (netstat -an)
|
||||||
|
-- | | | Proto Local Address Foreign Address State
|
||||||
|
-- | | | TCP 0.0.0.0:22 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:25 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:80 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:135 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:443 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:445 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:1025 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:1028 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:1029 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:3389 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 0.0.0.0:4933 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 10.0.0.30:139 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 127.0.0.1:2528 127.0.0.1:2529 ESTABLISHED
|
||||||
|
-- | | | TCP 127.0.0.1:2529 127.0.0.1:2528 ESTABLISHED
|
||||||
|
-- | | | TCP 127.0.0.1:2531 127.0.0.1:2532 ESTABLISHED
|
||||||
|
-- | | | TCP 127.0.0.1:2532 127.0.0.1:2531 ESTABLISHED
|
||||||
|
-- | | | TCP 127.0.0.1:5152 0.0.0.0:0 LISTENING
|
||||||
|
-- | | | TCP 127.0.0.1:5152 127.0.0.1:2530 CLOSE_WAIT
|
||||||
|
-- | | | UDP 0.0.0.0:135 *:*
|
||||||
|
-- | | | UDP 0.0.0.0:445 *:*
|
||||||
|
-- | | | UDP 0.0.0.0:1030 *:*
|
||||||
|
-- | | | UDP 0.0.0.0:3456 *:*
|
||||||
|
-- | | | UDP 10.0.0.30:137 *:*
|
||||||
|
-- | | | UDP 10.0.0.30:138 *:*
|
||||||
|
-- | | | UDP 10.0.0.30:500 *:*
|
||||||
|
-- | | | UDP 10.0.0.30:4500 *:*
|
||||||
|
-- | | |_ UDP 127.0.0.1:1026 *:*
|
||||||
|
-- | | Full routing table from 'netstat -nr'
|
||||||
|
-- | | | ===========================================================================
|
||||||
|
-- | | | Interface List
|
||||||
|
-- | | | 0x1 ........................... MS TCP Loopback interface
|
||||||
|
-- | | | 0x2 ...00 50 56 a1 00 65 ...... VMware Accelerated AMD PCNet Adapter
|
||||||
|
-- | | | 0x1000004 ...00 50 56 a1 24 c2 ...... VMware Accelerated AMD PCNet Adapter
|
||||||
|
-- | | | ===========================================================================
|
||||||
|
-- | | | ===========================================================================
|
||||||
|
-- | | | Active Routes:
|
||||||
|
-- | | | Network Destination Netmask Gateway Interface Metric
|
||||||
|
-- | | | 10.0.0.0 255.255.255.0 10.0.0.30 10.0.0.30 1
|
||||||
|
-- | | | 10.0.0.30 255.255.255.255 127.0.0.1 127.0.0.1 1
|
||||||
|
-- | | | 10.255.255.255 255.255.255.255 10.0.0.30 10.0.0.30 1
|
||||||
|
-- | | | 127.0.0.0 255.0.0.0 127.0.0.1 127.0.0.1 1
|
||||||
|
-- | | | 224.0.0.0 224.0.0.0 10.0.0.30 10.0.0.30 1
|
||||||
|
-- | | | 255.255.255.255 255.255.255.255 10.0.0.30 2 1
|
||||||
|
-- | | | ===========================================================================
|
||||||
|
-- | | | Persistent Routes:
|
||||||
|
-- | | | None
|
||||||
|
-- |_ |_ |_ Route Table
|
||||||
--
|
--
|
||||||
--@args config The config file to use (eg, default). Config files require a .lua extension, and are located in nselib/data/psexec.
|
--@args config The config file to use (eg, default). Config files require a .lua extension, and are located in nselib/data/psexec.
|
||||||
--@args nohide Don't set the uploaded files to hidden/system/etc.
|
--@args nohide Don't set the uploaded files to hidden/system/etc.
|
||||||
@@ -742,7 +792,8 @@ local function get_config(host)
|
|||||||
if(#missing_args > 0) then
|
if(#missing_args > 0) then
|
||||||
enabled = false
|
enabled = false
|
||||||
mod.disabled_message = {}
|
mod.disabled_message = {}
|
||||||
table.insert(mod.disabled_message, string.format("Configuration error: Required argument(s) ('%s') weren't given. Please add --script-args=[arg]=[value] to your commandline to run this module", stdnse.strjoin("', '", missing_args)))
|
table.insert(mod.disabled_message, string.format("Configuration error: Required argument(s) ('%s') weren't given.", stdnse.strjoin("', '", missing_args)))
|
||||||
|
table.insert(mod.disabled_message, string.format("Please add --script-args=[arg]=[value] to your commandline to run this module"))
|
||||||
if(#missing_args == 1) then
|
if(#missing_args == 1) then
|
||||||
table.insert(mod.disabled_message, string.format("For example: --script-args=%s=123", missing_args[1]))
|
table.insert(mod.disabled_message, string.format("For example: --script-args=%s=123", missing_args[1]))
|
||||||
else
|
else
|
||||||
@@ -763,6 +814,7 @@ local function get_config(host)
|
|||||||
if(mod.url) then
|
if(mod.url) then
|
||||||
stdnse.print_debug(1, "You can try getting it from: %s", mod.url)
|
stdnse.print_debug(1, "You can try getting it from: %s", mod.url)
|
||||||
table.insert(mod.disabled_message, string.format("You can try getting it from: %s", mod.url))
|
table.insert(mod.disabled_message, string.format("You can try getting it from: %s", mod.url))
|
||||||
|
table.insert(mod.disabled_message, "And placing it in Nmap's nselib/data/psexec/ directory")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- We found it
|
-- We found it
|
||||||
@@ -1189,7 +1241,7 @@ local function parse_output(config, data)
|
|||||||
-- If we're including it, do the replacements
|
-- If we're including it, do the replacements
|
||||||
if(include) then
|
if(include) then
|
||||||
line = do_replacements(mod, line)
|
line = do_replacements(mod, line)
|
||||||
table.insert(result['lines'], line)
|
table.insert(result, line)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1207,7 +1259,10 @@ local function parse_output(config, data)
|
|||||||
if(type(mod.disabled_message) == 'string') then
|
if(type(mod.disabled_message) == 'string') then
|
||||||
mod.disabled_message = {mod.disabled_message}
|
mod.disabled_message = {mod.disabled_message}
|
||||||
end
|
end
|
||||||
result['lines'] = mod.disabled_message
|
|
||||||
|
for _, message in ipairs(mod.disabled_message) do
|
||||||
|
table.insert(result, "WARNING: " .. message)
|
||||||
|
end
|
||||||
|
|
||||||
table.insert(results, result)
|
table.insert(results, result)
|
||||||
end
|
end
|
||||||
@@ -1215,31 +1270,7 @@ local function parse_output(config, data)
|
|||||||
return true, results
|
return true, results
|
||||||
end
|
end
|
||||||
|
|
||||||
---Convert the array generated by <code>parse_output</code> into something a little friendlier.
|
action = function(host)
|
||||||
local function results_to_string(results)
|
|
||||||
local response = " \n"
|
|
||||||
|
|
||||||
for _, mod in ipairs(results) do
|
|
||||||
response = response .. string.format("%s\n", mod.name)
|
|
||||||
|
|
||||||
for i = 1, #mod.lines, 1 do
|
|
||||||
if(i < #mod.lines) then
|
|
||||||
response = response .. string.format("| %s\n", mod.lines[i])
|
|
||||||
else
|
|
||||||
response = response .. string.format("|_%s\n", mod.lines[i])
|
|
||||||
response = response .. " \n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if(response == " \n") then
|
|
||||||
return ""
|
|
||||||
end
|
|
||||||
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
|
|
||||||
function go(host)
|
|
||||||
local status, result, err
|
local status, result, err
|
||||||
local key
|
local key
|
||||||
|
|
||||||
@@ -1253,7 +1284,7 @@ function go(host)
|
|||||||
-- Parse the configuration file
|
-- Parse the configuration file
|
||||||
status, config = get_config(host)
|
status, config = get_config(host)
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
return false, config
|
return stdnse.format_output(false, config)
|
||||||
end
|
end
|
||||||
|
|
||||||
if(#config.enabled_modules > 0) then
|
if(#config.enabled_modules > 0) then
|
||||||
@@ -1262,55 +1293,57 @@ function go(host)
|
|||||||
|
|
||||||
-- If the user just wanted a cleanup, do it
|
-- If the user just wanted a cleanup, do it
|
||||||
if(nmap.registry.args.cleanup == '1' or nmap.registry.args.cleanup == 'true') then
|
if(nmap.registry.args.cleanup == '1' or nmap.registry.args.cleanup == 'true') then
|
||||||
return true, "Cleanup complete"
|
return stdnse.format_output(true, "Cleanup complete.")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if any of the files exist
|
-- Check if any of the files exist
|
||||||
status, result, files = smb.files_exist(host, config.share, config.all_files, {})
|
status, result, files = smb.files_exist(host, config.share, config.all_files, {})
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
return false, "Couldn't log in to check for remote files: " .. result
|
return stdnse.format_output(false, "Couldn't log in to check for remote files: " .. result)
|
||||||
end
|
end
|
||||||
if(result > 0) then
|
if(result > 0) then
|
||||||
return false, "One or more output files already exist on the host, and couldn't be removed. Try:\n" ..
|
local response = {}
|
||||||
" * Running the script with --script-args=cleanup=1 to force a cleanup (passing -d and looking for error messages might hlep),\n" ..
|
table.insert(response, "One or more output files already exist on the host, and couldn't be removed. Try:")
|
||||||
" * Running the script with --script-args=randomseed=ABCD (or something) to change the name of the uploaded files,\n" ..
|
table.insert(response, "* Running the script with --script-args=cleanup=1 to force a cleanup (passing -d and looking for error messages might help),")
|
||||||
" * Changing the share and path using, for example, --script-args=share=C$,sharepath=C:, or\n" ..
|
table.insert(response, "* Running the script with --script-args=randomseed=ABCD (or something) to change the name of the uploaded files,")
|
||||||
" * Deleting the affected file(s) off the server manually (\\\\" .. config.share .. "\\" .. stdnse.strjoin(", \\\\" .. config.share .. "\\", files) .. ")"
|
table.insert(response, "* Changing the share and path using, for example, --script-args=share=C$,sharepath=C:, or")
|
||||||
|
table.insert(response, "* Deleting the affected file(s) off the server manually (\\\\" .. config.share .. "\\" .. stdnse.strjoin(", \\\\" .. config.share .. "\\", files) .. ")")
|
||||||
|
return stdnse.format_output(false, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Upload the modules
|
-- Upload the modules
|
||||||
status, err = upload_everything(host, config)
|
status, err = upload_everything(host, config)
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
cleanup(host, config)
|
cleanup(host, config)
|
||||||
return false, err
|
return stdnse.format_output(false, err)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Create the service
|
-- Create the service
|
||||||
status, err = create_service(host, config)
|
status, err = create_service(host, config)
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
cleanup(host, config)
|
cleanup(host, config)
|
||||||
return false, err
|
return stdnse.format_output(false, err)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get the table of parameters to pass to the service when we start it
|
-- Get the table of parameters to pass to the service when we start it
|
||||||
status, params = get_params(config)
|
status, params = get_params(config)
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
cleanup(host, config)
|
cleanup(host, config)
|
||||||
return false, params
|
return stdnse.format_output(false, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Start the service
|
-- Start the service
|
||||||
status, params = start_service(host, config, params)
|
status, params = start_service(host, config, params)
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
cleanup(host, config)
|
cleanup(host, config)
|
||||||
return false, params
|
return stdnse.format_output(false, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get the result
|
-- Get the result
|
||||||
status, result = get_output_file(host, config, config.share)
|
status, result = get_output_file(host, config, config.share)
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
cleanup(host, config)
|
cleanup(host, config)
|
||||||
return false, result
|
return stdnse.format_output(false, result)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Do a final cleanup
|
-- Do a final cleanup
|
||||||
@@ -1321,40 +1354,21 @@ function go(host)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Build the output into a nice table
|
-- Build the output into a nice table
|
||||||
status, results = parse_output(config, result)
|
status, response = parse_output(config, result)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
return false, "Couldn't parse output: " .. results
|
return stdnse.format_output(false, "Couldn't parse output: " .. results)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Parse the results into a pretty string
|
|
||||||
response = results_to_string(results)
|
|
||||||
|
|
||||||
-- Add a warning if nothing was enabled
|
-- Add a warning if nothing was enabled
|
||||||
if(#config.enabled_modules == 0) then
|
if(#config.enabled_modules == 0) then
|
||||||
if(#response == 0) then
|
if(#response == 0) then
|
||||||
response = "No modules were enabled! Please check your configuration file."
|
response = {"No modules were enabled! Please check your configuration file."}
|
||||||
else
|
else
|
||||||
response = response .. "\n\nNo modules were enabled! Please fix any errors displayed above, or check your configuration file."
|
table.insert(response, "No modules were enabled! Please fix any errors displayed above, or check your configuration file.")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Return the string
|
-- Return the string
|
||||||
return true, response
|
return stdnse.format_output(true, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
action = function(host)
|
|
||||||
|
|
||||||
local status, result = go(host)
|
|
||||||
|
|
||||||
if(status == false) then
|
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return "ERROR: " .. result
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,536 +0,0 @@
|
|||||||
description = [[
|
|
||||||
This script implements the functionality found in pwdump.exe, written by the Foofus group.
|
|
||||||
Essentially, it works by using pwdump6's modules (servpw.exe and lsremora.dll) to dump the
|
|
||||||
password hashes for a remote machine. This currently works against Windows 2000 and Windows
|
|
||||||
2003.
|
|
||||||
|
|
||||||
To run this script, the executable files for pwdump, servpw.exe and lsremora.dll, have to be
|
|
||||||
downloaded. These can be found at <http://foofus.net/fizzgig/pwdump/>, and version 1.6 has been
|
|
||||||
tested. Those two files should be placed in nmap's nselib data directory, <code>.../nselib/data/</code>.
|
|
||||||
Note that these files will likely trigger antivirus software -- if you want to get around that,
|
|
||||||
I recommend compiling your own version or obfuscating/encrypting/packing them (upx works wonders).
|
|
||||||
Another possible way around antivirus software is to change the filenames (especially on the remote
|
|
||||||
system -- triggering antivirus on the remote system can land you with some questions to answer). To do
|
|
||||||
that, simply change the <code>FILE*</code> constants in <code>smb-pwdump.nse</code>.
|
|
||||||
|
|
||||||
The hashes dumped are Lanman and NTLM, and they're in the format Lanman:NTLM. If one or the other
|
|
||||||
isn't set, it's indicated. These are the hashes that are stored in the SAM file on Windows,
|
|
||||||
and can be used in place of a password to log into systems (this technique is called "passing the
|
|
||||||
hash", and can be done in Nmap by using the <code>smbhash</code> argument instead of
|
|
||||||
<code>smbpassword</code> -- see <code>smbauth.lua</code> for more information.
|
|
||||||
|
|
||||||
In addition to directly using the hashes, the hashes can also be cracked. Hashes can be cracked
|
|
||||||
fairly easily with Rainbow Crack (rcrack) or John the Ripper (john). If you intend to crack the
|
|
||||||
hashes without smb-pwdump.nse's help, I suggest setting the <code>strict</code> parameter to '1', which
|
|
||||||
tells smb-pwdump.nse to print the hashes in pwdump format (except for the leading pipe '|', which
|
|
||||||
Nmap adds). Alternatively, you can tell the script to crack the passwords using the <code>rtable</code>
|
|
||||||
argument. For example:
|
|
||||||
<code>nmap -p445 --script=smb-pwdump --script-args=smbuser=ron,smbpass=iagotest2k3,rtable=/tmp/alpha/*.rt <host></code>
|
|
||||||
|
|
||||||
This assumes that 'rcrack' is installed in a standard place -- if not, the <code>rcrack</code> parameter
|
|
||||||
can be set to the path. The charset.txt file from Rainbow Crack may also have to be in the current
|
|
||||||
directory.
|
|
||||||
|
|
||||||
This script works by uploading the pwdump6 program to a fileshare, then establishing a connection
|
|
||||||
to the service control service (SVCCTL) and creating a new service, pointing to the pwdump6 program
|
|
||||||
(this sounds really invasive, but it's identical to how pwdump6, fgdump, psexec, etc. work). The service
|
|
||||||
runs, and sends back the data. Once the service is finished, the script will stop the service and
|
|
||||||
delete the files.
|
|
||||||
|
|
||||||
Obviously, this script is <em>highly</em> intrusive (and requires administrative privileges).
|
|
||||||
It's running a service on the remote machine (with SYSTEM-level access) to accomplish its goals,
|
|
||||||
and the service injects itself into the LSASS process to collect the needed information.
|
|
||||||
That being said, extra effort was focused on cleaning up. Unless something really bad happens
|
|
||||||
(which is always possible with a script like this), the service will be removed and the files
|
|
||||||
deleted.
|
|
||||||
|
|
||||||
Currently, this will only run against server versions of Windows (Windows 2000 and Windows 2003).
|
|
||||||
I (Ron Bowes) am hoping to make Windows XP work, but I've had nothing but trouble. Windows Vista
|
|
||||||
and higher won't ever work, because they disable the SVCCTL process.
|
|
||||||
|
|
||||||
This script was written mostly to highlight Nmap's growing potential as a pen-testing tool.
|
|
||||||
It complements the <code>smb-brute.nse</code> script because smb-brute can find weak administrator
|
|
||||||
passwords, then smb-pwdump.nse can use those passwords to dump hashes/passwords. Those can be added
|
|
||||||
to the password list for more brute forcing.
|
|
||||||
|
|
||||||
Since this tool can be dangerous, and can easily be viewed as a malicious tool, the usual
|
|
||||||
disclaimer applies -- use this responsibly, and especially don't break any laws with it.
|
|
||||||
|
|
||||||
]]
|
|
||||||
|
|
||||||
---
|
|
||||||
-- @usage
|
|
||||||
-- nmap --script smb-pwdump.nse --script-args=smbuser=<username>,smbpass=<password> -p445 <host>
|
|
||||||
-- sudo nmap -sU -sS --script smb-pwdump.nse --script-args=smbuser=<username>,smbpass=<password> -p U:137,T:139 <host>
|
|
||||||
--
|
|
||||||
-- @output
|
|
||||||
-- | smb-test:
|
|
||||||
-- | Administrator:500:D702A1D01B6BC2418112333D93DFBB4C:C8DBB1CFF1970C9E3EC44EBE2BA7CCBC:::
|
|
||||||
-- | ASPNET:1001:359E64F7361B678C283B72844ABF5707:49B784EF1E7AE06953E7A4D37A3E9529:::
|
|
||||||
-- | blankadmin:1003:NO PASSWORD*********************:NO PASSWORD*********************:::
|
|
||||||
-- | blankuser:1004:NO PASSWORD*********************:NO PASSWORD*********************:::
|
|
||||||
-- | Guest:501:NO PASSWORD*********************:NO PASSWORD*********************:::
|
|
||||||
-- | Ron:1000:D702A1D01B6BC2418112333D93DFBB4C:C8DBB1CFF1970C9E3EC44EBE2BA7CCBC:::
|
|
||||||
-- |_ test:1002:D702A1D01B6BC2418112333D93DFBB4C:C8DBB1CFF1970C9E3EC44EBE2BA7CCBC:::
|
|
||||||
--
|
|
||||||
-- @args rcrack Override the location checked for the Rainbow Crack program. By default, uses the default
|
|
||||||
-- directories searched by Lua (the $PATH variable, most likely)
|
|
||||||
-- @args rtable Set the path to the Rainbow Tables; for example, <code>/tmp/rainbow/*.rt</code>.
|
|
||||||
-- @args strict If set to '1', enable strict output. All output will be in pure pwdump format,
|
|
||||||
-- except for the leading pipe.
|
|
||||||
-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
author = "Ron Bowes"
|
|
||||||
copyright = "Ron Bowes"
|
|
||||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
|
||||||
categories = {"intrusive"}
|
|
||||||
|
|
||||||
require 'msrpc'
|
|
||||||
require 'smb'
|
|
||||||
require 'stdnse'
|
|
||||||
|
|
||||||
local SERVICE = "nmap-pwdump-"
|
|
||||||
local PIPE = "nmap-pipe-"
|
|
||||||
|
|
||||||
local FILE1 = "nselib/data/lsremora.dll"
|
|
||||||
local FILENAME1 = "lsremora.dll"
|
|
||||||
|
|
||||||
local FILE2 = "nselib/data/servpw.exe"
|
|
||||||
local FILENAME2 = "servpw.exe"
|
|
||||||
|
|
||||||
|
|
||||||
hostrule = function(host)
|
|
||||||
return smb.get_port(host) ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
---Stop/delete the service and delete the service file. This can be used alone to clean up the
|
|
||||||
-- pwdump stuff, if this crashes.
|
|
||||||
--
|
|
||||||
--@param host The host object.
|
|
||||||
--@param share The share to clean up on.
|
|
||||||
--@param path The local path to the share.
|
|
||||||
--@param service_name The name to use for the service.
|
|
||||||
function cleanup(host, share, path, service_name)
|
|
||||||
local status, err
|
|
||||||
|
|
||||||
stdnse.print_debug(1, "Entering cleanup('%s', '%s', '%s') -- errors here can generally be ignored", share, path, service_name)
|
|
||||||
-- Try stopping the service
|
|
||||||
status, err = msrpc.service_stop(host, SERVICE .. service_name)
|
|
||||||
if(status == false) then
|
|
||||||
stdnse.print_debug(1, "Couldn't stop service: %s", err)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Try deleting the service
|
|
||||||
status, err = msrpc.service_delete(host, SERVICE .. service_name)
|
|
||||||
if(status == false) then
|
|
||||||
stdnse.print_debug(1, "Couldn't delete service: %s", err)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Delete the files
|
|
||||||
status, err = smb.file_delete(host, share, "\\" .. FILENAME1)
|
|
||||||
if(status == false) then
|
|
||||||
stdnse.print_debug(1, "Couldn't delete %s: %s", FILENAME1, err)
|
|
||||||
end
|
|
||||||
|
|
||||||
status, err = smb.file_delete(host, share, "\\" .. FILENAME2)
|
|
||||||
if(status == false) then
|
|
||||||
stdnse.print_debug(1, "Couldn't delete %s: %s", FILENAME2, err)
|
|
||||||
end
|
|
||||||
|
|
||||||
stdnse.print_debug(1, "Leaving cleanup()")
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function upload_files(host, share)
|
|
||||||
local status, err
|
|
||||||
|
|
||||||
status, err = smb.file_upload(host, FILE1, share, "\\" .. FILENAME1)
|
|
||||||
if(status == false) then
|
|
||||||
cleanup(host)
|
|
||||||
return false, string.format("Couldn't upload %s: %s\n", FILE1, err)
|
|
||||||
end
|
|
||||||
|
|
||||||
status, err = smb.file_upload(host, FILE2, share, "\\" .. FILENAME2)
|
|
||||||
if(status == false) then
|
|
||||||
cleanup(host)
|
|
||||||
return false, string.format("Couldn't upload %s: %s\n", FILE2, err)
|
|
||||||
end
|
|
||||||
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function read_and_decrypt(host, key, pipe)
|
|
||||||
local status, smbstate
|
|
||||||
local results = {}
|
|
||||||
|
|
||||||
-- Create the SMB session
|
|
||||||
status, smbstate = msrpc.start_smb(host, msrpc.SVCCTL_PATH)
|
|
||||||
if(status == false) then
|
|
||||||
return false, smbstate
|
|
||||||
end
|
|
||||||
|
|
||||||
local i = 1
|
|
||||||
repeat
|
|
||||||
local status, wait_result, create_result, read_result, close_result
|
|
||||||
results[i] = {}
|
|
||||||
|
|
||||||
-- Wait for some data to show up on the pipe (there's a bit of a race condition here -- if this is called before the pipe is
|
|
||||||
-- created, it'll fail with a STATUS_OBJECT_NAME_NOT_FOUND.
|
|
||||||
|
|
||||||
local j = 1
|
|
||||||
repeat
|
|
||||||
status, wait_result = smb.send_transaction_waitnamedpipe(smbstate, 0, "\\PIPE\\" .. pipe)
|
|
||||||
if(status ~= false) then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
|
|
||||||
j = j + 1
|
|
||||||
-- Wait 50ms, if there's a time when we get an actual sleep()-style function.
|
|
||||||
stdnse.sleep(.05)
|
|
||||||
until status == true
|
|
||||||
|
|
||||||
if(j == 10) then
|
|
||||||
smbstop(smbstate)
|
|
||||||
return false, "WaitForNamedPipe() failed, service may not have been created properly."
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get a handle to the pipe
|
|
||||||
local overrides = {}
|
|
||||||
status, create_result = smb.create_file(smbstate, "\\" .. pipe, overrides)
|
|
||||||
if(status == false) then
|
|
||||||
smb.stop(smbstate)
|
|
||||||
return false, create_result
|
|
||||||
end
|
|
||||||
|
|
||||||
status, read_result = smb.read_file(smbstate, 0, 1000)
|
|
||||||
if(status == false) then
|
|
||||||
-- TODO: Figure out how to handle errors better
|
|
||||||
return false, read_result
|
|
||||||
else
|
|
||||||
local data = read_result['data']
|
|
||||||
local code = string.byte(string.sub(data, 1, 1))
|
|
||||||
if(code == 0) then
|
|
||||||
break
|
|
||||||
elseif(code == 2) then
|
|
||||||
local cUserBlocks = string.byte(string.sub(data, 3, 3))
|
|
||||||
local userblock = ""
|
|
||||||
for j = 0, cUserBlocks, 1 do
|
|
||||||
local _, a, b = bin.unpack("<II", data, 68 + (j * 8))
|
|
||||||
local encrypted = bin.pack(">II", a, b)
|
|
||||||
local decrypted_hex = openssl.decrypt("blowfish", key, nil, encrypted)
|
|
||||||
_, a, b = bin.unpack("<II", decrypted_hex)
|
|
||||||
userblock = userblock .. bin.pack(">II", a, b)
|
|
||||||
end
|
|
||||||
|
|
||||||
local password_block = ""
|
|
||||||
for j = 0, 3, 1 do
|
|
||||||
local _, a, b = bin.unpack("<II", data, 4 + (j * 8))
|
|
||||||
local encrypted = bin.pack(">II", a, b)
|
|
||||||
local decrypted_hex = openssl.decrypt("blowfish", key, nil, encrypted)
|
|
||||||
_, a, b = bin.unpack("<II", decrypted_hex)
|
|
||||||
password_block = password_block .. bin.pack(">II", a, b)
|
|
||||||
end
|
|
||||||
|
|
||||||
_, results[i]['username'] = bin.unpack("z", userblock)
|
|
||||||
_, results[i]['ntlm'] = bin.unpack("H16", password_block)
|
|
||||||
_, results[i]['lm'] = bin.unpack("H16", password_block, 17)
|
|
||||||
|
|
||||||
if(results[i]['lm'] == "AAD3B435B51404EEAAD3B435B51404EE") then
|
|
||||||
results[i]['lm'] = "NO PASSWORD*********************"
|
|
||||||
end
|
|
||||||
|
|
||||||
if(results[i]['ntlm'] == "31D6CFE0D16AE931B73C59D7E0C089C0") then
|
|
||||||
results[i]['ntlm'] = "NO PASSWORD*********************"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
stdnse.print_debug(1, "Unknown message code from pwdump: %d", code)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
status, close_result = smb.close_file(smbstate)
|
|
||||||
if(status == false) then
|
|
||||||
smb.stop(smbstate)
|
|
||||||
return false, close_result
|
|
||||||
end
|
|
||||||
i = i + 1
|
|
||||||
until(1 == 2)
|
|
||||||
|
|
||||||
smb.stop(smbstate)
|
|
||||||
|
|
||||||
return true, results
|
|
||||||
end
|
|
||||||
|
|
||||||
-- TODO: Check for OpenSSL
|
|
||||||
function go(host)
|
|
||||||
local status, err
|
|
||||||
local results
|
|
||||||
local key
|
|
||||||
|
|
||||||
local key = ""
|
|
||||||
local i
|
|
||||||
|
|
||||||
local result
|
|
||||||
local service_name
|
|
||||||
local share, path
|
|
||||||
|
|
||||||
result, service_name = smb.get_uniqueish_name(host)
|
|
||||||
if(result == false) then
|
|
||||||
return false, string.format("Error generating service name: %s", service_name)
|
|
||||||
end
|
|
||||||
stdnse.print_debug("pwdump: Generated static service name: %s", service_name)
|
|
||||||
|
|
||||||
-- Try and find a share to use.
|
|
||||||
result, share, path = smb.share_find_writable(host)
|
|
||||||
if(result == false) then
|
|
||||||
return false, string.format("Couldn't find a writable share: %s", share)
|
|
||||||
end
|
|
||||||
if(path == nil) then
|
|
||||||
return false, string.format("Couldn't find path to writable share (we probably don't have admin access): '%s'", share)
|
|
||||||
end
|
|
||||||
stdnse.print_debug(1, "pwdump: Found usable share %s (%s)", share, path)
|
|
||||||
|
|
||||||
-- Start by cleaning up, just in case.
|
|
||||||
cleanup(host, share, path, service_name)
|
|
||||||
|
|
||||||
-- It seems that, in my tests, if a key contains either a null byte or a negative byte (>= 0x80), errors
|
|
||||||
-- happen. So, at the cost of generating a weaker key (keeping in mind that it's already sent over the
|
|
||||||
-- network), we're going to generate a key from printable characters only (we could use 0x01 to 0x1F
|
|
||||||
-- without error, but eh? Debugging is easier when you can type the key in)
|
|
||||||
local key_bytes = openssl.rand_bytes(16)
|
|
||||||
for i = 1, 16, 1 do
|
|
||||||
key = key .. string.char((string.byte(string.sub(key_bytes, i, i)) % 0x5F) + 0x20)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Upload the files
|
|
||||||
status, err = upload_files(host, share)
|
|
||||||
if(status == false) then
|
|
||||||
stdnse.print_debug(1, "Couldn't upload the files: %s", err)
|
|
||||||
cleanup(host, share, path, service_name)
|
|
||||||
return false, string.format("Couldn't upload the files: %s", err)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Create the service
|
|
||||||
status, err = msrpc.service_create(host, SERVICE .. service_name, path .. "\\servpw.exe")
|
|
||||||
if(status == false) then
|
|
||||||
stdnse.print_debug(1, "Couldn't create the service: %s", err)
|
|
||||||
cleanup(host, share, path, service_name)
|
|
||||||
|
|
||||||
return false, string.format("Couldn't create the service on the remote machine: %s", err)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Start the service
|
|
||||||
status, err = msrpc.service_start(host, SERVICE .. service_name, {PIPE .. service_name, key, tostring(string.char(16)), tostring(string.char(0)), "servpw.exe"})
|
|
||||||
if(status == false) then
|
|
||||||
stdnse.print_debug(1, "Couldn't start the service: %s", err)
|
|
||||||
cleanup(host, share, path, service_name)
|
|
||||||
|
|
||||||
return false, string.format("Couldn't start the service on the remote machine: %s", err)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Read the data
|
|
||||||
status, results = read_and_decrypt(host, key, PIPE .. service_name)
|
|
||||||
if(status == false) then
|
|
||||||
stdnse.print_debug(1, "Error reading data from remote service")
|
|
||||||
cleanup(host, share, path, service_name)
|
|
||||||
|
|
||||||
return false, string.format("Failed to read password data from the remote service: %s", err)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Clean up what we did
|
|
||||||
cleanup(host, share, path, service_name)
|
|
||||||
|
|
||||||
return true, results
|
|
||||||
end
|
|
||||||
|
|
||||||
---Converts an array of accounts to a pwdump-like representation.
|
|
||||||
--@param accounts The accounts array. It should have a list of tables, each with 'username', 'lm', and 'ntlm'.
|
|
||||||
--@param strict If 'strict' is set to true, a true pwdump representation wiill be used; otherwise, a more user friendly one will.
|
|
||||||
--@return A string in the standard pwdump format.
|
|
||||||
function accounts_to_pwdump(accounts, strict)
|
|
||||||
local str = ""
|
|
||||||
|
|
||||||
for i=1, #accounts, 1 do
|
|
||||||
if(accounts[i]['username'] ~= nil) then
|
|
||||||
if(strict) then
|
|
||||||
str = str .. string.format("%s:%s:%s:::\n", accounts[i]['username'], accounts[i]['lm'], accounts[i]['ntlm'])
|
|
||||||
else
|
|
||||||
if(accounts[i]['password']) then
|
|
||||||
str = str .. string.format("%s => %s:%s (Password: %s)\n", accounts[i]['username'], accounts[i]['lm'], accounts[i]['ntlm'], accounts[i]['password'])
|
|
||||||
else
|
|
||||||
str = str .. string.format("%s => %s:%s\n", accounts[i]['username'], accounts[i]['lm'], accounts[i]['ntlm'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
---Run the 'rcrack' program and parse the output. This may sound simple, but the output of rcrack clearly
|
|
||||||
-- wasn't designed to be scriptable, so it's a little difficult. But, it works, at least for 1.2.
|
|
||||||
function rainbow(accounts, rcrack, rtable)
|
|
||||||
local pwdump = accounts_to_pwdump(accounts, true)
|
|
||||||
local pwdump_file = os.tmpname()
|
|
||||||
local file
|
|
||||||
local command = rcrack .. " " .. rtable .. " -f " .. pwdump_file
|
|
||||||
|
|
||||||
-- Print a warning if 'charset.txt' isn't present
|
|
||||||
file = io.open("charset.txt", "r")
|
|
||||||
if(file == nil) then
|
|
||||||
stdnse.print_debug(1, "WARNING: 'charset.txt' not found in current directory; rcrack may not run properly")
|
|
||||||
else
|
|
||||||
io.close(file)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Create the pwdump file
|
|
||||||
stdnse.print_debug(1, "Creating the temporary pwdump file (%s)", pwdump_file)
|
|
||||||
file, err = io.open(pwdump_file, "w")
|
|
||||||
if(file == nil) then
|
|
||||||
return false, err
|
|
||||||
end
|
|
||||||
file:write(pwdump)
|
|
||||||
file:close()
|
|
||||||
|
|
||||||
-- Start up rcrack
|
|
||||||
stdnse.print_debug(1, "Starting rcrack (%s)", command)
|
|
||||||
file, err = io.popen(command, "r")
|
|
||||||
if(file == nil) then
|
|
||||||
return false, err
|
|
||||||
end
|
|
||||||
|
|
||||||
for line in file:lines() do
|
|
||||||
stdnse.print_debug(2, "RCRACK: %s\n", line)
|
|
||||||
if(string.find(line, "hex:") ~= nil) then
|
|
||||||
local start_hex1 = 0
|
|
||||||
local start_hex2 = 0
|
|
||||||
local hex1, hex2
|
|
||||||
local ascii1, ascii2
|
|
||||||
local password
|
|
||||||
local i
|
|
||||||
|
|
||||||
-- First, find the last place in the string that starts with "hex:"
|
|
||||||
repeat
|
|
||||||
local _, pos = string.find(line, " hex:", start_hex1)
|
|
||||||
if(pos ~= nil) then
|
|
||||||
start_hex1 = pos + 1
|
|
||||||
end
|
|
||||||
until pos == nil
|
|
||||||
|
|
||||||
-- Get the first part of the hex
|
|
||||||
if(string.sub(line, start_hex1, start_hex1 + 9) == "<notfound>") then
|
|
||||||
-- If it wasn't found, then set it as such and go to after the "not found" part
|
|
||||||
ascii1 = "<notfound>"
|
|
||||||
start_hex2 = start_hex1 + 10
|
|
||||||
else
|
|
||||||
-- If it was found, convert to ascii
|
|
||||||
ascii1 = bin.pack("H", string.sub(line, start_hex1, start_hex1 + 13))
|
|
||||||
start_hex2 = start_hex1 + 14
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Get the second part of the hex
|
|
||||||
if(string.sub(line, start_hex2) == "") then
|
|
||||||
ascii2 = ""
|
|
||||||
elseif(string.sub(line, start_hex2, start_hex2 + 9) == "<notfound>") then
|
|
||||||
-- It wasn't found
|
|
||||||
ascii2 = "<notfound>"
|
|
||||||
else
|
|
||||||
-- It was found, convert to ascii
|
|
||||||
ascii2 = bin.pack("H", string.sub(line, start_hex2, start_hex2 + 13))
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Join the two halves of the password together
|
|
||||||
password = ascii1 .. ascii2
|
|
||||||
|
|
||||||
-- Figure out the username (it's the part that is followed by a bunch of spaces then the password)
|
|
||||||
i = string.find(line, " +" .. password)
|
|
||||||
|
|
||||||
username = string.sub(line, 1, i - 1)
|
|
||||||
|
|
||||||
-- Finally, find the username in the account table and add our entry
|
|
||||||
for i=1, #accounts, 1 do
|
|
||||||
if(accounts[i]['username'] ~= nil) then
|
|
||||||
if(string.find(accounts[i]['username'], username .. ":%d+$") ~= nil) then
|
|
||||||
accounts[i]['password'] = password
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Close the process handle
|
|
||||||
file:close()
|
|
||||||
|
|
||||||
-- Remove the pwdump file
|
|
||||||
os.remove(pwdump_file)
|
|
||||||
|
|
||||||
return true, accounts
|
|
||||||
end
|
|
||||||
|
|
||||||
action = function(host)
|
|
||||||
|
|
||||||
local status, results
|
|
||||||
local response = " \n"
|
|
||||||
local rcrack = "rcrack"
|
|
||||||
local rtable = nil
|
|
||||||
|
|
||||||
-- Check if we have the necessary files
|
|
||||||
if(nmap.fetchfile(FILE1) == nil or nmap.fetchfile(FILE2) == nil) then
|
|
||||||
local err = " \n"
|
|
||||||
err = err .. string.format("Couldn't run smb-pwdump.nse, missing required file(s):\n")
|
|
||||||
if(nmap.fetchfile(FILE1) == nil) then
|
|
||||||
err = err .. "- " .. FILE1 .. "\n"
|
|
||||||
end
|
|
||||||
if(nmap.fetchfile(FILE2) == nil) then
|
|
||||||
err = err .. "- " .. FILE2 .. "\n"
|
|
||||||
end
|
|
||||||
err = err .. string.format("These are included in pwdump6 version 1.7.2:\n")
|
|
||||||
err = err .. string.format("<http://foofus.net/fizzgig/pwdump/downloads.htm>")
|
|
||||||
|
|
||||||
return err
|
|
||||||
end
|
|
||||||
|
|
||||||
status, results = go(host)
|
|
||||||
|
|
||||||
if(status == false) then
|
|
||||||
return "ERROR: " .. results
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Only try cracking if strict is turned off
|
|
||||||
if(nmap.registry.args.strict == nil) then
|
|
||||||
-- Override the rcrack program
|
|
||||||
if(nmap.registry.args.rcrack ~= nil) then
|
|
||||||
rcrack = nmap.registry.args.rcrack
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if a table was passed
|
|
||||||
if(nmap.registry.args.rtable ~= nil) then
|
|
||||||
rtable = nmap.registry.args.rtable
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check a spelling mistake that I keep making
|
|
||||||
if(nmap.registry.args.rtables ~= nil) then
|
|
||||||
rtable = nmap.registry.args.rtables
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Check if we actually got a table
|
|
||||||
if(rtable ~= nil) then
|
|
||||||
status, crack_results = rainbow(results, rcrack, rtable)
|
|
||||||
if(status == false) then
|
|
||||||
response = "ERROR cracking: " .. crack_results .. "\n"
|
|
||||||
else
|
|
||||||
results = crack_results
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
response = response .. accounts_to_pwdump(results, false)
|
|
||||||
else
|
|
||||||
response = response .. accounts_to_pwdump(results, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
return response
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
@@ -18,19 +18,18 @@ set the username and password, etc.), but it probably won't ever require them.
|
|||||||
-- sudo nmap -sU -sS --script smb-security-mode.nse -p U:137,T:139 127.0.0.1
|
-- sudo nmap -sU -sS --script smb-security-mode.nse -p U:137,T:139 127.0.0.1
|
||||||
--
|
--
|
||||||
--@output
|
--@output
|
||||||
-- | smb-security-mode: User-level authentication
|
-- Host script results:
|
||||||
-- | smb-security-mode: Challenge/response passwords supported
|
-- | smb-security-mode:
|
||||||
-- |_ smb-security-mode: Message signing supported
|
-- | | Account that was used for smb scripts: administrator
|
||||||
|
-- | | User-level authentication
|
||||||
|
-- | | SMB Security: Challenge/response passwords supported
|
||||||
|
-- |_ |_ Message signing disabled (dangerous, but default)
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "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"
|
||||||
categories = {"discovery", "safe"}
|
categories = {"discovery", "safe"}
|
||||||
|
|
||||||
-- Set the runlevel to above 1 to ensure this runs after the bulk of the scripts. That lets us more effectively
|
|
||||||
-- find out which account we've been using.
|
|
||||||
runlevel = 1.01
|
|
||||||
|
|
||||||
require 'smb'
|
require 'smb'
|
||||||
require 'stdnse'
|
require 'stdnse'
|
||||||
|
|
||||||
@@ -48,58 +47,49 @@ action = function(host)
|
|||||||
|
|
||||||
status, state = smb.start(host)
|
status, state = smb.start(host)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, state)
|
||||||
return "ERROR: " .. state
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
status, err = smb.negotiate_protocol(state, overrides)
|
status, err = smb.negotiate_protocol(state, overrides)
|
||||||
|
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
smb.stop(state)
|
smb.stop(state)
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, err)
|
||||||
return "ERROR: " .. err
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local security_mode = state['security_mode']
|
local security_mode = state['security_mode']
|
||||||
|
|
||||||
local response = ""
|
local response = {}
|
||||||
|
|
||||||
local result, username, domain = smb.get_account(host)
|
local result, username, domain = smb.get_account(host)
|
||||||
if(result ~= false) then
|
if(result ~= false) then
|
||||||
response = string.format("Account that was used for smb scripts: %s\%s\n", domain, stdnse.string_or_blank(username, '<blank>'))
|
table.insert(response, string.format("Account that was used for smb scripts: %s\%s\n", domain, stdnse.string_or_blank(username, '<blank>')))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- User-level authentication or share-level authentication
|
-- User-level authentication or share-level authentication
|
||||||
if(bit.band(security_mode, 1) == 1) then
|
if(bit.band(security_mode, 1) == 1) then
|
||||||
response = response .. "User-level authentication\n"
|
table.insert(response, "User-level authentication")
|
||||||
else
|
else
|
||||||
response = response .. " Share-level authentication\n"
|
table.insert(response, "Share-level authentication (dangerous)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Challenge/response supported?
|
-- Challenge/response supported?
|
||||||
if(bit.band(security_mode, 2) == 0) then
|
if(bit.band(security_mode, 2) == 0) then
|
||||||
response = response .. "SMB Security: Plaintext only\n"
|
table.insert(response, "Plaintext passwords required (dangerous)")
|
||||||
else
|
else
|
||||||
response = response .. "SMB Security: Challenge/response passwords supported\n"
|
table.insert(response, "SMB Security: Challenge/response passwords supported")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Message signing supported/required?
|
-- Message signing supported/required?
|
||||||
if(bit.band(security_mode, 8) == 8) then
|
if(bit.band(security_mode, 8) == 8) then
|
||||||
response = response .. "SMB Security: Message signing required\n"
|
table.insert(response, "Message signing required")
|
||||||
elseif(bit.band(security_mode, 4) == 4) then
|
elseif(bit.band(security_mode, 4) == 4) then
|
||||||
response = response .. "SMB Security: Message signing supported\n"
|
table.insert(response, "Message signing supported")
|
||||||
else
|
else
|
||||||
response = response .. "SMB Security: Message signing not supported\n"
|
table.insert(response, "Message signing disabled (dangerous, but default)")
|
||||||
end
|
end
|
||||||
|
|
||||||
smb.stop(state)
|
smb.stop(state)
|
||||||
return response
|
return stdnse.format_output(true, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ up to version 1.0.3 (and possibly higher).
|
|||||||
-- @output
|
-- @output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-server-stats:
|
-- | smb-server-stats:
|
||||||
-- | Server statistics collected since 2008-12-12 14:53:27 (89d17h37m48s):
|
-- | | Server statistics collected since 2009-09-22 09:56:00 (48d5h53m36s):
|
||||||
-- | |_ 22884718 bytes (2.95 b/s) sent, 28082489 bytes (3.62 b/s) received
|
-- | | | 6513655 bytes (1.56 b/s) sent, 40075383 bytes (9.61 b/s) received
|
||||||
-- |_ |_ 5759 failed logins, 16 permission errors, 0 system errors, 0 print jobs, 1273 files opened
|
-- |_ |_ |_ 19323 failed logins, 179 permission errors, 0 system errors, 0 print jobs, 2921 files opened
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -42,23 +42,21 @@ end
|
|||||||
action = function(host)
|
action = function(host)
|
||||||
|
|
||||||
local result, stats
|
local result, stats
|
||||||
local response = " \n"
|
local response = {}
|
||||||
|
local subresponse = {}
|
||||||
|
|
||||||
result, stats = msrpc.get_server_stats(host)
|
result, stats = msrpc.get_server_stats(host)
|
||||||
|
|
||||||
if(result == false) then
|
if(result == false) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, response)
|
||||||
return "ERROR: " .. stats
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
response = response .. string.format("Server statistics collected since %s (%s):\n", stats['start_str'], stats['period_str'])
|
table.insert(response, string.format("Server statistics collected since %s (%s):", stats['start_str'], stats['period_str']))
|
||||||
response = response .. string.format("|_ %d bytes (%.2f b/s) sent, %d bytes (%.2f b/s) received\n", stats['bytessent'], stats['bytessentpersecond'], stats['bytesrcvd'], stats['bytesrcvdpersecond'])
|
table.insert(subresponse, string.format("%d bytes (%.2f b/s) sent, %d bytes (%.2f b/s) received", stats['bytessent'], stats['bytessentpersecond'], stats['bytesrcvd'], stats['bytesrcvdpersecond']))
|
||||||
response = response .. string.format("|_ %d failed logins, %d permission errors, %d system errors, %d print jobs, %d files opened\n", stats['pwerrors'], stats['permerrors'], stats['syserrors'], stats['jobsqueued'], stats['fopens'])
|
table.insert(subresponse, string.format("%d failed logins, %d permission errors, %d system errors, %d print jobs, %d files opened", stats['pwerrors'], stats['permerrors'], stats['syserrors'], stats['jobsqueued'], stats['fopens']))
|
||||||
|
table.insert(response, subresponse)
|
||||||
|
|
||||||
return response
|
return stdnse.format_output(true, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,26 +26,20 @@ the system, besides showing a message box to the user.
|
|||||||
-- @output
|
-- @output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- | smb-system-info:
|
-- | smb-system-info:
|
||||||
-- | OS Details
|
-- | | OS Details
|
||||||
-- | |_ Microsoft Windows Server 2003 Service Pack 2 (ServerNT 5.2 build 3790)
|
-- | | | Microsoft Windows 2000 Service Pack 4 (ServerNT 5.0 build 2195)
|
||||||
-- | |_ Installed on 2007-11-26 23:40:40
|
-- | | | Installed on 2008-10-10 05:47:19
|
||||||
-- | |_ Registered to Ron Bowes (organization: MYCOMPANY)
|
-- | | | Registered to Ron (organization: Government of Manitoba)
|
||||||
-- | |_ Path: %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;C:\Program Files\Microsoft SQL Server\90\Tools\binn\;C:\Program Files\IBM\Rational AppScan\
|
-- | | | Path: %SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;C:\Program Files\Graphviz2.20\Bin;
|
||||||
-- | |_ Systemroot: C:\WINDOWS
|
-- | | | Systemroot: C:\WINNT
|
||||||
-- | |_ Page files: C:\pagefile.sys 2046 4092 (cleared at shutdown => 0)
|
-- | | |_ Page files: C:\pagefile.sys 192 384 (cleared at shutdown => 0)
|
||||||
-- | Hardware
|
-- | | Hardware
|
||||||
-- | |_ CPU 0: Intel(R) Xeon(TM) CPU 2.80GHz [2780mhz GenuineIntel]
|
-- | | | CPU 0: Intel(R) Xeon(TM) CPU 2.80GHz [2800mhz GenuineIntel]
|
||||||
-- | |_ Identifier 0: x86 Family 15 Model 2 Stepping 9
|
-- | | | |_ Identifier 0: x86 Family 15 Model 3 Stepping 8
|
||||||
-- | |_ CPU 1: Intel(R) Xeon(TM) CPU 2.80GHz [2780mhz GenuineIntel]
|
-- | | |_ Video driver: VMware SVGA II
|
||||||
-- | |_ Identifier 1: x86 Family 15 Model 2 Stepping 9
|
-- | | Browsers
|
||||||
-- | |_ CPU 2: Intel(R) Xeon(TM) CPU 2.80GHz [2780mhz GenuineIntel]
|
-- | | | Internet Explorer 6.0000
|
||||||
-- | |_ Identifier 2: x86 Family 15 Model 2 Stepping 9
|
-- |_ |_ |_ Firefox 3.0.12 (en-US)
|
||||||
-- | |_ 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)
|
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -59,6 +53,8 @@ require 'msrpc'
|
|||||||
require 'smb'
|
require 'smb'
|
||||||
require 'stdnse'
|
require 'stdnse'
|
||||||
|
|
||||||
|
-- TODO: This script needs some love
|
||||||
|
|
||||||
hostrule = function(host)
|
hostrule = function(host)
|
||||||
return smb.get_port(host) ~= nil
|
return smb.get_port(host) ~= nil
|
||||||
end
|
end
|
||||||
@@ -183,59 +179,62 @@ action = function(host)
|
|||||||
status, result = get_info_registry(host)
|
status, result = get_info_registry(host)
|
||||||
|
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
return stdnse.format_output(false, result)
|
||||||
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
|
|
||||||
if(result['status-processornamestring'..i] == false) then
|
|
||||||
result['status-processornamestring'..i] = "Unknown"
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
local response = {}
|
||||||
|
|
||||||
|
if(result['status-os'] == true) then
|
||||||
|
local osdetails = {}
|
||||||
|
osdetails['name'] = "OS Details"
|
||||||
|
table.insert(osdetails, string.format("%s %s (%s %s build %s)", result['productname'], result['csdversion'], result['producttype'], result['currentversion'], result['currentbuildnumber']))
|
||||||
|
table.insert(osdetails, string.format("Installed on %s", result['installdate']))
|
||||||
|
table.insert(osdetails, string.format("Registered to %s (organization: %s)", result['registeredowner'], result['registeredorganization']))
|
||||||
|
table.insert(osdetails, string.format("Path: %s", result['path']))
|
||||||
|
table.insert(osdetails, string.format("Systemroot: %s", result['systemroot']))
|
||||||
|
table.insert(osdetails, string.format("Page files: %s (cleared at shutdown => %s)", result['pagingfiles'], result['clearpagefileatshutdown']))
|
||||||
|
table.insert(response, osdetails)
|
||||||
|
|
||||||
|
local hardware = {}
|
||||||
|
hardware['name'] = "Hardware"
|
||||||
|
for i = 0, result['number_of_processors'] - 1, 1 do
|
||||||
|
if(result['status-processornamestring'..i] == false) then
|
||||||
|
result['status-processornamestring'..i] = "Unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
local processor = {}
|
||||||
|
processor['name'] = string.format("CPU %d: %s [%dmhz %s]", i, string.gsub(result['processornamestring'..i], ' ', ''), result['~mhz'..i], result['vendoridentifier'..i])
|
||||||
|
table.insert(processor, string.format("Identifier %d: %s", i, result['identifier'..i]))
|
||||||
|
table.insert(hardware, processor)
|
||||||
|
end
|
||||||
|
table.insert(hardware, string.format("Video driver: %s", result['video_driverdesc']))
|
||||||
|
table.insert(response, hardware)
|
||||||
|
|
||||||
|
local browsers = {}
|
||||||
|
browsers['name'] = "Browsers"
|
||||||
|
table.insert(browsers, string.format("Internet Explorer %s", result['ie_version']))
|
||||||
|
if(result['status-ff_version']) then
|
||||||
|
table.insert(browsers, string.format("Firefox %s", result['ff_version']))
|
||||||
|
end
|
||||||
|
table.insert(response, browsers)
|
||||||
|
|
||||||
|
return stdnse.format_output(true, response)
|
||||||
|
elseif(result['status-productname'] == true) then
|
||||||
|
|
||||||
|
local osdetails = {}
|
||||||
|
osdetails['name'] = 'OS Details'
|
||||||
|
osdetails['warning'] = "Access was denied for certain values; try an administrative account for more complete information"
|
||||||
|
|
||||||
|
table.insert(osdetails, string.format("%s %s (%s %s build %s)", result['productname'], result['csdversion'], result['producttype'], result['currentversion'], result['currentbuildnumber']))
|
||||||
|
table.insert(osdetails, string.format("Installed on %s", result['installdate']))
|
||||||
|
table.insert(osdetails, string.format("Registered to %s (organization: %s)", result['registeredowner'], result['registeredorganization']))
|
||||||
|
table.insert(osdetails, string.format("Systemroot: %s", result['systemroot']))
|
||||||
|
table.insert(response, osdetails)
|
||||||
|
|
||||||
|
return stdnse.format_output(true, response)
|
||||||
|
end
|
||||||
|
|
||||||
|
return stdnse.format_output(false, "Account being used was unable to probe for information, try using an administrative account")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -137,19 +137,19 @@ local brute_cred = function(user, pass, soc)
|
|||||||
return 1, "error -> this should never happen"
|
return 1, "error -> this should never happen"
|
||||||
end
|
end
|
||||||
|
|
||||||
local function go(host, port)
|
action = function(host, port)
|
||||||
local pair, status
|
local pair, status
|
||||||
local user, pass, count, rbuf
|
local user, pass, count, rbuf
|
||||||
local usernames, passwords
|
local usernames, passwords
|
||||||
|
|
||||||
status, usernames = unpwdb.usernames()
|
status, usernames = unpwdb.usernames()
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
return false, usernames
|
stdnse.format_output(false, usernames)
|
||||||
end
|
end
|
||||||
|
|
||||||
status, passwords = unpwdb.passwords()
|
status, passwords = unpwdb.passwords()
|
||||||
if(not(status)) then
|
if(not(status)) then
|
||||||
return false, passwords
|
return stdnse.format_output(false, passwords)
|
||||||
end
|
end
|
||||||
|
|
||||||
pair = nil
|
pair = nil
|
||||||
@@ -160,7 +160,7 @@ local function go(host, port)
|
|||||||
|
|
||||||
local soc, line, best_opt = comm.tryssl(host, port, "\n",opts)
|
local soc, line, best_opt = comm.tryssl(host, port, "\n",opts)
|
||||||
if not soc then
|
if not soc then
|
||||||
return false, "Unable to open connection"
|
return stdnse.format_output(false, "Unable to open connection")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- continually try user/pass pairs (reconnecting, if we have to)
|
-- continually try user/pass pairs (reconnecting, if we have to)
|
||||||
@@ -176,7 +176,7 @@ local function go(host, port)
|
|||||||
pass = passwords()
|
pass = passwords()
|
||||||
|
|
||||||
if(not(pass)) then
|
if(not(pass)) then
|
||||||
return false, "No accounts found"
|
return stdnse.format_output(true, "No accounts found")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -199,21 +199,9 @@ local function go(host, port)
|
|||||||
|
|
||||||
status, pair = brute_cred(user, pass, soc)
|
status, pair = brute_cred(user, pass, soc)
|
||||||
end
|
end
|
||||||
|
|
||||||
soc:close()
|
soc:close()
|
||||||
return true, pair
|
|
||||||
|
return pair
|
||||||
end
|
end
|
||||||
|
|
||||||
action = function(host, port)
|
|
||||||
|
|
||||||
local status, result = go(host, port)
|
|
||||||
|
|
||||||
if(not(status)) then
|
|
||||||
if(nmap.debugging() > 0) then
|
|
||||||
return "ERROR: " .. result
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user