mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 12:41:29 +00:00
Merged in significant changes to Microsoft RPC calls
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o Complete re-write of the marshalling logic for Microsoft RPC calls.
|
||||||
|
[Ron Bowes]
|
||||||
|
|
||||||
|
o Added vulnerability checks for MS08-067 as well as an unfixed
|
||||||
|
denial of service in the Windows 2000 registry service.
|
||||||
|
[Ron Bowes]
|
||||||
|
|
||||||
o Zenmap now runs ndiff to do its "Compare Results" function. This
|
o Zenmap now runs ndiff to do its "Compare Results" function. This
|
||||||
completely replaces the old diff view. ndiff is now required to do
|
completely replaces the old diff view. ndiff is now required to do
|
||||||
comparisons in Zenmap. [David]
|
comparisons in Zenmap. [David]
|
||||||
|
|||||||
1853
nselib/msrpc.lua
1853
nselib/msrpc.lua
File diff suppressed because it is too large
Load Diff
3921
nselib/msrpctypes.lua
Normal file
3921
nselib/msrpctypes.lua
Normal file
File diff suppressed because it is too large
Load Diff
62
nselib/nmapdebug.lua
Normal file
62
nselib/nmapdebug.lua
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
--- Debugging functions for Nmap scripts.
|
||||||
|
--
|
||||||
|
-- This module contains various handy functions for debugging. These should
|
||||||
|
-- never be used for actual results, only during testing.
|
||||||
|
--
|
||||||
|
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
||||||
|
|
||||||
|
local require = require
|
||||||
|
local type = type
|
||||||
|
local pairs = pairs
|
||||||
|
local nmap = require "nmap";
|
||||||
|
local stdnse = require "stdnse";
|
||||||
|
|
||||||
|
local EMPTY = {}; -- Empty constant table
|
||||||
|
|
||||||
|
module(... or "nmapdebug");
|
||||||
|
|
||||||
|
---Converts an arbitrary data type into a string. Will recursively convert
|
||||||
|
-- tables. This can be very useful for debugging.
|
||||||
|
--
|
||||||
|
--@param data The data to convert.
|
||||||
|
--@param indent (optional) The number of times to indent the line. Default
|
||||||
|
-- is 0.
|
||||||
|
--@return A string representation of a data, will be one or more full lines.
|
||||||
|
function tostr(data, indent)
|
||||||
|
local str = ""
|
||||||
|
|
||||||
|
if(indent == nil) then
|
||||||
|
indent = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check the type
|
||||||
|
if(type(data) == "nil") then
|
||||||
|
str = str .. (" "):rep(indent) .. data .. "\n"
|
||||||
|
elseif(type(data) == "string") then
|
||||||
|
str = str .. (" "):rep(indent) .. data .. "\n"
|
||||||
|
elseif(type(data) == "number") then
|
||||||
|
str = str .. (" "):rep(indent) .. data .. "\n"
|
||||||
|
elseif(type(data) == "boolean") then
|
||||||
|
if(data == true) then
|
||||||
|
str = str .. "true"
|
||||||
|
else
|
||||||
|
str = str .. "false"
|
||||||
|
end
|
||||||
|
elseif(type(data) == "table") then
|
||||||
|
local i, v
|
||||||
|
for i, v in pairs(data) do
|
||||||
|
-- Check for a table in a table
|
||||||
|
if(type(v) == "table") then
|
||||||
|
str = str .. (" "):rep(indent) .. i .. ":\n"
|
||||||
|
str = str .. tostr(v, indent + 2)
|
||||||
|
else
|
||||||
|
str = str .. (" "):rep(indent) .. i .. ": " .. tostr(v, 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
stdnse.print_debug(1, "Error: unknown data type: %s", type(data))
|
||||||
|
end
|
||||||
|
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
104
nselib/smb.lua
104
nselib/smb.lua
@@ -1,10 +1,18 @@
|
|||||||
--- Server Message Block (SMB, also known as CIFS) traffic.
|
--- Server Message Block (SMB, also known as CIFS) traffic.
|
||||||
--
|
--
|
||||||
-- SMB traffic is normally
|
-- SMB traffic is normally sent to/from ports 139 or 445 of Windows systems, some of them
|
||||||
-- sent to/from ports 139 or 445 of Windows systems, although it's also implemented by
|
-- properly and many of them not. Samba implements it, as do many printers and other embedded
|
||||||
-- other systems (the most notable one being Samba).
|
-- devices. Although the protocol has been documented decently well by Samba and others,
|
||||||
|
-- many 3rd party implementations are broken or make assumptions.
|
||||||
|
--
|
||||||
|
-- Naturally, I do the same; however, that being said, this has been tested against every
|
||||||
|
-- broken implementation we could find, and will accept (or fail gracefully) against any
|
||||||
|
-- bad implementations we could find.
|
||||||
|
--
|
||||||
|
-- The intention of this library is to eventually handle all aspects of the SMB protocol.
|
||||||
|
-- That being said, I'm only implementing the pieces that I find myself needing. If you
|
||||||
|
-- require something more, let me know and I'll put it on my todo list.
|
||||||
--
|
--
|
||||||
-- The intention of this library is to eventually handle all aspects of the SMB protocol,
|
|
||||||
-- A programmer using this library must already have some knowledge of the SMB protocol,
|
-- A programmer using this library must already have some knowledge of the SMB protocol,
|
||||||
-- although a lot isn't necessary. You can pick up a lot by looking at the code that uses
|
-- although a lot isn't necessary. You can pick up a lot by looking at the code that uses
|
||||||
-- this. The basic login/logoff is this:
|
-- this. The basic login/logoff is this:
|
||||||
@@ -31,13 +39,14 @@
|
|||||||
-- status, err = smb.negotiate_protocol(smbstate)
|
-- status, err = smb.negotiate_protocol(smbstate)
|
||||||
-- status, err = smb.start_session(smbstate)
|
-- status, err = smb.start_session(smbstate)
|
||||||
-- status, err = smb.tree_connect(smbstate, path)
|
-- status, err = smb.tree_connect(smbstate, path)
|
||||||
|
-- ...
|
||||||
-- status, err = smb.tree_disconnect(smbstate)
|
-- status, err = smb.tree_disconnect(smbstate)
|
||||||
-- status, err = smb.logoff(smbstate)
|
-- status, err = smb.logoff(smbstate)
|
||||||
-- status, err = smb.stop(smbstate)
|
-- status, err = smb.stop(smbstate)
|
||||||
--</code>
|
--</code>
|
||||||
--
|
--
|
||||||
-- The <code>stop</code> function will automatically call tree_disconnect and logoff,
|
-- The <code>stop</code> function will automatically call tree_disconnect and logoff,
|
||||||
-- cleaning up the session.
|
-- cleaning up the session, if it hasn't been done already.
|
||||||
--
|
--
|
||||||
-- To initially begin the connection, there are two options:
|
-- To initially begin the connection, there are two options:
|
||||||
--
|
--
|
||||||
@@ -48,11 +57,11 @@
|
|||||||
-- That packet requires the computer's name, which is requested
|
-- That packet requires the computer's name, which is requested
|
||||||
-- using a NBSTAT probe over UDP port 137.
|
-- using a NBSTAT probe over UDP port 137.
|
||||||
--
|
--
|
||||||
-- Once it's connected, a <code>SMB_COM_NEGOTIATE</code> packet is sent,
|
-- Once it's connected, a <code>SMB_COM_NEGOTIATE</code> packet is sent, requesting the protocol
|
||||||
-- requesting the protocol "NT LM 0.12", which is the most commonly
|
-- "NT LM 0.12", which is the most commonly supported one. Among other things, the server's
|
||||||
-- supported one. Among other things, the server's response contains
|
-- response contains the host's security level, the system time, and the computer/domain name.
|
||||||
-- the host's security level, the system time, and the computer/domain
|
-- Some systems will refuse to use that protocol and return "-1" or "1" instead of 0. If that's
|
||||||
-- name.
|
-- detected, we kill the connection (because the protocol following will be unexpected).
|
||||||
--
|
--
|
||||||
-- If that's successful, <code>SMB_COM_SESSION_SETUP_ANDX</code> is sent. It is essentially the logon
|
-- If that's successful, <code>SMB_COM_SESSION_SETUP_ANDX</code> is sent. It is essentially the logon
|
||||||
-- packet, where the username, domain, and password are sent to the server for verification.
|
-- packet, where the username, domain, and password are sent to the server for verification.
|
||||||
@@ -67,13 +76,13 @@
|
|||||||
-- any further (<code>tree_connect</code> might fail).
|
-- any further (<code>tree_connect</code> might fail).
|
||||||
--
|
--
|
||||||
-- In terms of the login protocol, by default, we sent only NTLMv1 authentication, Lanman
|
-- In terms of the login protocol, by default, we sent only NTLMv1 authentication, Lanman
|
||||||
-- isn't set. The reason for this is, NTLMv2 isn't supported by every system (and I don't know
|
-- isn't sent at all. The reason for this is, NTLMv2 isn't supported by every system (and I
|
||||||
-- how to do message signing on the v2 protocols), and doesn't have a significant security
|
-- don't know how to do message signing on the v2 protocols), and doesn't have a significant security
|
||||||
-- advantage over NTLMv1 (the major change in NTLMv2 is incorporating a client challenge).
|
-- advantage over NTLMv1 for performing single scans (the major change in NTLMv2 is incorporating
|
||||||
-- Lanman is horribly insecure, though, so I don't send it at all. These options can, however,
|
-- a client challenge). Lanman is somewhat insecure, though, so I don't send it at all. These options
|
||||||
-- be overridden either through script parameters or registry settings [TODO].
|
-- can, however, be overridden either through script parameters or registry settings.
|
||||||
--
|
--
|
||||||
-- Lanman v1 is a fairly weak protocol, although it's still fairly difficult to reverse. NTLMv1 is a slightly more secure
|
-- Lanman v1 is a fairly weak protocol, although it's still fairly difficult to break. NTLMv1 is a slightly more secure
|
||||||
-- protocol (although not much) -- it's also fairly difficult to reverse, though. Windows clients, by default send LMv1 and
|
-- protocol (although not much) -- it's also fairly difficult to reverse, though. Windows clients, by default send LMv1 and
|
||||||
-- NTLMv1 together, but every modern Windows server will accept NTLM alone, so I opted to use that. LMv2 and NTLMv2 are
|
-- NTLMv1 together, but every modern Windows server will accept NTLM alone, so I opted to use that. LMv2 and NTLMv2 are
|
||||||
-- slightly more secure, and they let the client specify random data (to help fight malicious servers with pre-
|
-- slightly more secure, and they let the client specify random data (to help fight malicious servers with pre-
|
||||||
@@ -82,24 +91,8 @@
|
|||||||
--
|
--
|
||||||
-- Another interesting aspect of the password hashing is that the original password isn't even necessary, the
|
-- Another interesting aspect of the password hashing is that the original password isn't even necessary, the
|
||||||
-- password's hash can be used instead. This hash can be dumped from memory of a live system by tools such as
|
-- password's hash can be used instead. This hash can be dumped from memory of a live system by tools such as
|
||||||
-- pwdump and fgdump, or read straight from the SAM file. This means that if a password file is recovered,
|
-- pwdump and fgdump, or read straight from the SAM file (maybe some day, I'll do an Nmap script to dump it).
|
||||||
-- it doesn't even need to be cracked before it can be used here.
|
-- This means that if a password file is recovered, it doesn't even need to be cracked before it can be used here.
|
||||||
--
|
|
||||||
-- The response to <code>SMB_COM_SESSION_SETUP_ANDX</code> is fairly simple, containing a boolean for
|
|
||||||
-- success, along with the operating system and the lan manager name.
|
|
||||||
--
|
|
||||||
-- After a successful <code>SMB_COM_SESSION_SETUP_ANDX</code> has been made, a
|
|
||||||
-- <code>SMB_COM_TREE_CONNECT_ANDX</code> packet can be sent. This is what connects to a share.
|
|
||||||
-- The server responds to this with a boolean answer, and little more information.
|
|
||||||
--
|
|
||||||
-- Each share will either return <code>STATUS_BAD_NETWORK_NAME</code> if the share doesn't
|
|
||||||
-- exist, <code>STATUS_ACCESS_DENIED</code> if it exists but we don't have access, or
|
|
||||||
-- <code>STATUS_SUCCESS</code> if exists and we do have access. <code>STATUS_ACCESS_DENIED</code> is also returned
|
|
||||||
-- if the server requires message signing and we don't return a valid signature.
|
|
||||||
--
|
|
||||||
-- Once we're connected to a share, we can start doing other operations like reading/writing files
|
|
||||||
-- or calling RPC functions. Calling RPC functions is the interesting part, and it's done through
|
|
||||||
-- the <code>SMB_TRANS</code> packet. The actual RPC protocol is built on top of the SMB protocol.
|
|
||||||
--
|
--
|
||||||
-- Thanks go to Christopher R. Hertel and his book Implementing CIFS, which
|
-- Thanks go to Christopher R. Hertel and his book Implementing CIFS, which
|
||||||
-- taught me everything I know about Microsoft's protocols. Additionally, I used Samba's
|
-- taught me everything I know about Microsoft's protocols. Additionally, I used Samba's
|
||||||
@@ -109,7 +102,6 @@
|
|||||||
--
|
--
|
||||||
-- Scripts that use this module can use the script arguments
|
-- Scripts that use this module can use the script arguments
|
||||||
-- <code>smbusername</code>, <code>smbpassword</code>, <code>smbhash</code>,
|
-- <code>smbusername</code>, <code>smbpassword</code>, <code>smbhash</code>,
|
||||||
-- <code>smbguest</code>, and <code>smbtype</code>, described below. Here's an
|
|
||||||
-- example of using these script arguments:
|
-- example of using these script arguments:
|
||||||
-- <code>
|
-- <code>
|
||||||
-- nmap --script=smb-<script>.nse --script-args=smbuser=ron,smbpass=iagotest2k3 <host>
|
-- nmap --script=smb-<script>.nse --script-args=smbuser=ron,smbpass=iagotest2k3 <host>
|
||||||
@@ -128,9 +120,6 @@
|
|||||||
-- single character). These hashes are the LanMan or NTLM hash of the user's password,
|
-- single character). These hashes are the LanMan or NTLM hash of the user's password,
|
||||||
-- and are stored on disk or in memory. They can be retrieved from memory
|
-- and are stored on disk or in memory. They can be retrieved from memory
|
||||||
-- using the fgdump or pwdump tools.
|
-- using the fgdump or pwdump tools.
|
||||||
--@args smbguest If this is set to <code>true</code> or <code>1</code>, a guest login will be attempted if the normal one
|
|
||||||
-- fails. This should be harmless, but I thought I would disable it by default anyway
|
|
||||||
-- because I'm not entirely sure of any possible consequences.
|
|
||||||
--@args smbtype The type of SMB authentication to use. These are the possible options:
|
--@args smbtype The type of SMB authentication to use. These are the possible options:
|
||||||
-- * <code>v1</code>: Sends LMv1 and NTLMv1.
|
-- * <code>v1</code>: Sends LMv1 and NTLMv1.
|
||||||
-- * <code>LMv1</code>: Sends LMv1 only.
|
-- * <code>LMv1</code>: Sends LMv1 only.
|
||||||
@@ -160,6 +149,7 @@ status_codes = {}
|
|||||||
status_names = {}
|
status_names = {}
|
||||||
|
|
||||||
local mutexes = setmetatable({}, {__mode = "k"});
|
local mutexes = setmetatable({}, {__mode = "k"});
|
||||||
|
--local debug_mutex = nmap.mutex("SMB-DEBUG")
|
||||||
|
|
||||||
---Returns the mutex that should be used by the current connection. This mutex attempts
|
---Returns the mutex that should be used by the current connection. This mutex attempts
|
||||||
-- to use the name, first, then falls back to the IP if no name was returned.
|
-- to use the name, first, then falls back to the IP if no name was returned.
|
||||||
@@ -170,6 +160,10 @@ local function get_mutex(smbstate)
|
|||||||
local mutex_name = "SMB-"
|
local mutex_name = "SMB-"
|
||||||
local mutex
|
local mutex
|
||||||
|
|
||||||
|
-- if(nmap.debugging() > 0) then
|
||||||
|
-- return debug_mutex
|
||||||
|
-- end
|
||||||
|
|
||||||
-- Decide whether to use the name or the ip address as the unique identifier
|
-- Decide whether to use the name or the ip address as the unique identifier
|
||||||
if(smbstate['name'] ~= nil) then
|
if(smbstate['name'] ~= nil) then
|
||||||
mutex_name = mutex_name .. smbstate['name']
|
mutex_name = mutex_name .. smbstate['name']
|
||||||
@@ -842,11 +836,13 @@ function smb_read(smb)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- The length of the packet is 4 bytes of big endian (for our purposes).
|
-- The length of the packet is 4 bytes of big endian (for our purposes).
|
||||||
-- The NetBIOS header is 4 bytes, big endian
|
-- The NetBIOS header is 24 bits, big endian
|
||||||
pos, netbios_length = bin.unpack(">I", result)
|
pos, netbios_length = bin.unpack(">I", result)
|
||||||
if(netbios_length == nil) then
|
if(netbios_length == nil) then
|
||||||
return false, "SMB: Malformed packet received"
|
return false, "SMB: Malformed packet received"
|
||||||
end
|
end
|
||||||
|
-- Make the length 24 bits
|
||||||
|
netbios_length = bit.band(netbios_length, 0x00FFFFFF)
|
||||||
|
|
||||||
-- The total length is the netbios_length, plus 4 (for the length itself)
|
-- The total length is the netbios_length, plus 4 (for the length itself)
|
||||||
length = netbios_length + 4
|
length = netbios_length + 4
|
||||||
@@ -1116,7 +1112,7 @@ local function get_domain(domain)
|
|||||||
stdnse.print_debug(2, "SMB: Using domain passed as an nmap parameter: %s", domain)
|
stdnse.print_debug(2, "SMB: Using domain passed as an nmap parameter: %s", domain)
|
||||||
else
|
else
|
||||||
domain = ""
|
domain = ""
|
||||||
stdnse.print_debug(2, "SMB: couldn't find domain to use, using blank")
|
stdnse.print_debug(2, "SMB: Couldn't find domain to use, using blank")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1185,7 +1181,7 @@ local function get_password_response(username, domain, password, password_hash,
|
|||||||
lm_hash = bin.pack("H", hashes:sub(1, 32))
|
lm_hash = bin.pack("H", hashes:sub(1, 32))
|
||||||
ntlm_hash = bin.pack("H", hashes:sub(34, 65))
|
ntlm_hash = bin.pack("H", hashes:sub(34, 65))
|
||||||
else
|
else
|
||||||
stdnse.print_debug(1, "SMB: Hash(es) provided in an invalid format (should be 32, 64, or 65 hex characters)")
|
stdnse.print_debug(1, "SMB: ERROR: Hash(es) provided in an invalid format (should be 32, 64, or 65 hex characters)")
|
||||||
lm_hash = nil
|
lm_hash = nil
|
||||||
ntlm_hash = nil
|
ntlm_hash = nil
|
||||||
end
|
end
|
||||||
@@ -1294,12 +1290,11 @@ local function get_logins(ip, challenge, username, domain, password, password_ha
|
|||||||
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
stdnse.print_debug(1, "SMB: Couldn't find OpenSSL library, only checking Guest and/or Anonymous accounts")
|
stdnse.print_debug(1, "SMB: ERROR: Couldn't find OpenSSL library, only checking Guest and/or Anonymous accounts")
|
||||||
end
|
end
|
||||||
|
|
||||||
local data
|
local data
|
||||||
-- Add guest account, if they requested
|
-- Add guest account
|
||||||
if(nmap.registry.args.smbguest == "true" or nmap.registry.args.smbguest == "1") then
|
|
||||||
stdnse.print_debug(2, "SMB: Going to try guest account before attempting anonymous")
|
stdnse.print_debug(2, "SMB: Going to try guest account before attempting anonymous")
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
@@ -1308,7 +1303,6 @@ local function get_logins(ip, challenge, username, domain, password, password_ha
|
|||||||
data['lanman'] = ''
|
data['lanman'] = ''
|
||||||
data['ntlm'] = ''
|
data['ntlm'] = ''
|
||||||
response[#response + 1] = data
|
response[#response + 1] = data
|
||||||
end
|
|
||||||
|
|
||||||
-- Add the anonymous account
|
-- Add the anonymous account
|
||||||
data = {}
|
data = {}
|
||||||
@@ -1423,9 +1417,9 @@ function start_session(smb, username, domain, password, password_hash, hash_type
|
|||||||
|
|
||||||
-- Check if they're using an un-supported system [TODO: once I sort this out, remove the warning]
|
-- Check if they're using an un-supported system [TODO: once I sort this out, remove the warning]
|
||||||
if(os == nil or lanmanager == nil or domain == nil) then
|
if(os == nil or lanmanager == nil or domain == nil) then
|
||||||
stdnse.print_debug(1, "SMB: Warning: the server is using a non-standard SMB implementation; your mileage may vary (%s)", smb['ip'])
|
stdnse.print_debug(1, "SMB: WARNING: the server is using a non-standard SMB implementation; your mileage may vary (%s)", smb['ip'])
|
||||||
elseif(os == "Unix" or string.sub(lanmanager, 1, 5) == "Samba") then
|
elseif(os == "Unix" or string.sub(lanmanager, 1, 5) == "Samba") then
|
||||||
stdnse.print_debug(1, "SMB: Warning: the server apperas to be Unix; your mileage may vary.")
|
stdnse.print_debug(1, "SMB: WARNING: the server appears to be Unix; your mileage may vary.")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Check if they were logged in as a guest
|
-- Check if they were logged in as a guest
|
||||||
@@ -1443,7 +1437,7 @@ function start_session(smb, username, domain, password, password_hash, hash_type
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
stdnse.print_debug(1, "SMB: All logins failed, sorry it didn't work out!")
|
stdnse.print_debug(1, "SMB: ERROR: All logins failed, sorry it didn't work out!")
|
||||||
return false, get_status_name(status)
|
return false, get_status_name(status)
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -1751,14 +1745,17 @@ function send_transaction(smb, func, function_parameters, function_data)
|
|||||||
return false, "SMB: Malformed packet received"
|
return false, "SMB: Malformed packet received"
|
||||||
end
|
end
|
||||||
if(status ~= 0) then
|
if(status ~= 0) then
|
||||||
io.write(string.format("Status = %08x\n\n", status))
|
if(status_names[status] == nil) then
|
||||||
return false, status_codes[status]
|
return false, string.format("Unknown SMB error: 0x%08x\n", status)
|
||||||
|
else
|
||||||
|
return false, status_names[status]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Parse the parameters
|
-- Parse the parameters
|
||||||
pos, total_word_count, total_data_count, reserved1, parameter_count, parameter_offset, parameter_displacement, data_count, data_offset, data_displacement, setup_count, reserved2 = bin.unpack("<SSSSSSSSSCC", parameters)
|
pos, total_word_count, total_data_count, reserved1, parameter_count, parameter_offset, parameter_displacement, data_count, data_offset, data_displacement, setup_count, reserved2 = bin.unpack("<SSSSSSSSSCC", parameters)
|
||||||
if(reserved2 == nil) then
|
if(reserved2 == nil) then
|
||||||
return "SMB: Malformed packet received"
|
return false, "SMB: Malformed packet received"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Convert the parameter/data offsets into something more useful (the offset into the data section)
|
-- Convert the parameter/data offsets into something more useful (the offset into the data section)
|
||||||
@@ -1864,15 +1861,18 @@ end
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
status_names =
|
status_codes =
|
||||||
{
|
{
|
||||||
NT_STATUS_OK = 0x0000,
|
NT_STATUS_OK = 0x0000,
|
||||||
NT_STATUS_WERR_BADFILE = 0x00000002,
|
NT_STATUS_WERR_BADFILE = 0x00000002,
|
||||||
NT_STATUS_WERR_ACCESS_DENIED = 0x00000005,
|
NT_STATUS_WERR_ACCESS_DENIED = 0x00000005,
|
||||||
NT_STATUS_WERR_INVALID_NAME = 0x0000007b,
|
NT_STATUS_WERR_INVALID_NAME = 0x0000007b,
|
||||||
|
NT_STATUS_WERR_UNKNOWN_LEVEL = 0x0000007c,
|
||||||
NT_STATUS_NO_MORE_ITEMS = 0x00000103,
|
NT_STATUS_NO_MORE_ITEMS = 0x00000103,
|
||||||
NT_STATUS_MORE_ENTRIES = 0x00000105,
|
NT_STATUS_MORE_ENTRIES = 0x00000105,
|
||||||
NT_STATUS_SOME_NOT_MAPPED = 0x00000107,
|
NT_STATUS_SOME_NOT_MAPPED = 0x00000107,
|
||||||
|
DOS_STATUS_UNKNOWN_ERROR = 0x00010001,
|
||||||
|
DOS_STATUS_ACCESS_DENIED = 0x00050001,
|
||||||
NT_STATUS_BUFFER_OVERFLOW = 0x80000005,
|
NT_STATUS_BUFFER_OVERFLOW = 0x80000005,
|
||||||
NT_STATUS_UNSUCCESSFUL = 0xc0000001,
|
NT_STATUS_UNSUCCESSFUL = 0xc0000001,
|
||||||
NT_STATUS_NOT_IMPLEMENTED = 0xc0000002,
|
NT_STATUS_NOT_IMPLEMENTED = 0xc0000002,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ local nmap = require"nmap";
|
|||||||
local max = math.max
|
local max = math.max
|
||||||
local ceil = math.ceil
|
local ceil = math.ceil
|
||||||
local type = type
|
local type = type
|
||||||
|
local pairs = pairs
|
||||||
|
|
||||||
local EMPTY = {}; -- Empty constant table
|
local EMPTY = {}; -- Empty constant table
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,36 @@ Checks if a host is vulnerable to MS08-067, a Windows RPC vulnerability that
|
|||||||
can allow remote code execution. This script is intended to check for more
|
can allow remote code execution. This script is intended to check for more
|
||||||
vulnerabilities in the future.
|
vulnerabilities in the future.
|
||||||
|
|
||||||
Checking for MS08-067 is very dangerous, as the check is likely
|
WARNING: These checks are dangerous, and are very likely to bring down a server.
|
||||||
to crash systems. On a fairly wide scan conducted by Brandon Enright, we determined
|
These should not be run in a production environment unless you (and, more importantly,
|
||||||
|
the business) understand the risks!
|
||||||
|
|
||||||
|
If you set the script parameter 'unsafe', then scripts will run that are almost
|
||||||
|
(or totally) guaranteed to crash a vulnerable system; do NOT specify <code>unsafe</code>
|
||||||
|
in a production environment! And that isn't to say that non-unsafe scripts will
|
||||||
|
not crash a system, they're just less likely to.
|
||||||
|
|
||||||
|
MS08-067 -- Checks if a host is vulnerable to MS08-067, a Windows RPC vulnerability that
|
||||||
|
can allow remote code execution. Checking for MS08-067 is very dangerous, as the check
|
||||||
|
is likelyto crash systems. On a fairly wide scan conducted by Brandon Enright, we determined
|
||||||
that on average, a vulnerable system is more likely to crash than to survive
|
that on average, a vulnerable system is more likely to crash than to survive
|
||||||
the check. Out of 82 vulnerable systems, 52 crashed. As such, great care should be
|
the check. Out of 82 vulnerable systems, 52 crashed.
|
||||||
taken when using this check.
|
|
||||||
|
|
||||||
You have the option to supply a username and password, but
|
At the same time, MS08-067 is extremely critical to fix. Metasploit has a working and
|
||||||
it shouldn't be necessary for a default configuration.
|
stable exploit for it, and any system vulnerable can very easily be compromised.
|
||||||
|
|
||||||
|
regsvc DoS -- Checks if a host is vulnerable to a crash in regsvc, caused
|
||||||
|
by a null pointer dereference. I inadvertently discovered this crash while working
|
||||||
|
on <code>smb-enum-sessions</code>, and discovered that it was repeatable. It's been
|
||||||
|
reported to Microsoft (case #MSRC8742).
|
||||||
|
|
||||||
|
This check WILL crash the service, if it's vulnerable, and requires a guest account
|
||||||
|
or higher to work. It is considered <code>unsafe</code>.
|
||||||
|
|
||||||
|
(Note: if you have other SMB/MSRPC vulnerability checks you'd like to see added, and
|
||||||
|
you can show me a tool with a license that is compatible with Nmap's, post a request
|
||||||
|
on the Nmap-dev mailing list and I'll add it to my list [Ron Bowes]).
|
||||||
]]
|
]]
|
||||||
|
|
||||||
-- Currently, this script checks if a host is vulnerable to ms08-067. I'd like to add
|
-- Currently, this script checks if a host is vulnerable to ms08-067. I'd like to add
|
||||||
-- checks for more vulnerabilities, but I'm worried about licensing/copyright issues
|
-- checks for more vulnerabilities, but I'm worried about licensing/copyright issues
|
||||||
-- (since I'd be basing them on non-free tools).
|
-- (since I'd be basing them on non-free tools).
|
||||||
@@ -25,11 +45,16 @@ it shouldn't be necessary for a default configuration.
|
|||||||
--
|
--
|
||||||
--@output
|
--@output
|
||||||
-- Host script results:
|
-- Host script results:
|
||||||
-- |_ smb-check-vulns: This host is vulnerable to MS08-067
|
-- | smb-check-vulns:
|
||||||
|
-- | MS08-067: FIXED
|
||||||
|
-- |_ regsvc DoS: VULNERABLE
|
||||||
--
|
--
|
||||||
-- @args smb* This script supports the <code>smbusername</code>,
|
-- @args smb* This script supports the <code>smbusername</code>,
|
||||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
-- script arguments of the <code>smb</code> module.
|
||||||
|
-- @args unsafe If set, this script will run checks that, if the system isn't
|
||||||
|
-- patched, are basically guaranteed to crash something. Remember that
|
||||||
|
-- non-unsafe checks aren't necessarily safe either)
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -48,6 +73,7 @@ end
|
|||||||
local VULNERABLE = 1
|
local VULNERABLE = 1
|
||||||
local PATCHED = 2
|
local PATCHED = 2
|
||||||
local UNKNOWN = 3
|
local UNKNOWN = 3
|
||||||
|
local NOTRUN = 4
|
||||||
|
|
||||||
---Check if the server is patched for MS08-067. This is done by calling NetPathCompare with an
|
---Check if the server is patched for MS08-067. This is done by calling NetPathCompare with an
|
||||||
-- illegal string. If the string is accepted, then the server is vulnerable; if it's rejected, then
|
-- illegal string. If the string is accepted, then the server is vulnerable; if it's rejected, then
|
||||||
@@ -58,7 +84,7 @@ local UNKNOWN = 3
|
|||||||
--
|
--
|
||||||
-- If there's a licensing issue, please let me (Ron Bowes) know so I can
|
-- If there's a licensing issue, please let me (Ron Bowes) know so I can
|
||||||
--
|
--
|
||||||
-- NOTE: This CAN crash stuff (ie, crash svchost.exe and force a reboot), so beware! In about 20
|
-- NOTE: This CAN crash stuff (ie, crash svchost and force a reboot), so beware! In about 20
|
||||||
-- tests I did, it crashed once. This is not a guarantee.
|
-- tests I did, it crashed once. This is not a guarantee.
|
||||||
--
|
--
|
||||||
--@param host The host object.
|
--@param host The host object.
|
||||||
@@ -104,32 +130,108 @@ function check_ms08_067(host)
|
|||||||
return true, VULNERABLE
|
return true, VULNERABLE
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---While writing <code>smb-enum-sessions</code> I discovered a repeatable null-pointer dereference
|
||||||
|
-- in regsvc. I reported it to Microsoft, but because it's a simple DoS (and barely even that, because
|
||||||
|
-- the service automatically restarts), and because it's only in Windows 2000, it isn't likely that they'll
|
||||||
|
-- fix it. This function checks for that crash (by crashing the process).
|
||||||
|
--
|
||||||
|
-- The crash occurs when the string sent to winreg_enumkey() function is null.
|
||||||
|
--
|
||||||
|
--@param host The host object.
|
||||||
|
--@return (status, result) If status if alse, result is an error code; otherwise, result is either
|
||||||
|
-- <code>VULNERABLE</code> for vulnerable or <code>PATCHED</code> for not vulnerable. If the check
|
||||||
|
-- was skipped, <code>NOTRUN</code> is returned.
|
||||||
|
function check_winreg_Enum_crash(host)
|
||||||
|
local i, j
|
||||||
|
local elements = {}
|
||||||
|
|
||||||
|
if(nmap.registry.args.unsafe == nil) then
|
||||||
|
return true, NOTRUN
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create the SMB session
|
||||||
|
status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH)
|
||||||
|
if(status == false) then
|
||||||
|
return false, smbstate
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Bind to WINREG service
|
||||||
|
status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil)
|
||||||
|
if(status == false) then
|
||||||
|
msrpc.stop_smb(smbstate)
|
||||||
|
return false, bind_result
|
||||||
|
end
|
||||||
|
|
||||||
|
status, openhku_result = msrpc.winreg_openhku(smbstate)
|
||||||
|
if(status == false) then
|
||||||
|
msrpc.stop_smb(smbstate)
|
||||||
|
return false, openhku_result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Loop through the keys under HKEY_USERS and grab the names
|
||||||
|
status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], 0, nil)
|
||||||
|
msrpc.stop_smb(smbstate)
|
||||||
|
|
||||||
|
if(status == false) then
|
||||||
|
return true, VULNERABLE
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, PATCHED
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
action = function(host)
|
action = function(host)
|
||||||
local status, result
|
local status, result
|
||||||
|
local response = " \n"
|
||||||
|
local found = false
|
||||||
|
|
||||||
|
-- Check for ms08-067
|
||||||
status, result = check_ms08_067(host)
|
status, result = check_ms08_067(host)
|
||||||
|
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
if(nmap.debugging() > 0) then
|
||||||
return "ERROR: " .. result
|
return "MS08-067: ERROR: " .. result
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if(result == VULNERABLE) then
|
if(result == VULNERABLE) then
|
||||||
response = "This host is vulnerable to MS08-067"
|
response = response .. "MS08-067: VULNERABLE\n"
|
||||||
|
found = true
|
||||||
elseif(result == UNKNOWN) then
|
elseif(result == UNKNOWN) then
|
||||||
response = "This host is likely vulnerable to MS08-067 (it stopped responding during the test)"
|
response = response .. "MS08-067: LIKELY VULNERABLE (host stopped responding)\n"
|
||||||
else
|
else
|
||||||
if(nmap.verbosity() > 0) then
|
if(nmap.verbosity() > 0) then
|
||||||
response = "This host is patched for MS08-067"
|
response = response .. "MS08-067: FIXED\n"
|
||||||
else
|
|
||||||
response = nil
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Check for a winreg_Enum crash
|
||||||
|
status, result = check_winreg_Enum_crash(host)
|
||||||
|
if(status == false) then
|
||||||
|
if(nmap.debugging() > 0) then
|
||||||
|
return response .. "regsvc DoS: ERROR: " .. result
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if(result == VULNERABLE) then
|
||||||
|
response = response .. "regsvc DoS: VULNERABLE\n"
|
||||||
|
found = true
|
||||||
|
elseif(result == NOTRUN) then
|
||||||
|
if(nmap.verbosity() > 0) then
|
||||||
|
response = response .. "regsvc DoS: NOT RUN (add --script-args=unsafe=1 to run)\n"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if(nmap.verbosity() > 0) then
|
||||||
|
response = response .. "regsvc DoS: FIXED\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Don't show a response if we aren't verbose and we found nothing
|
||||||
|
if(nmap.verbosity() == 0 and found == false) then
|
||||||
|
response = nil
|
||||||
|
end
|
||||||
|
|
||||||
return response
|
return response
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ After the initial <code>bind</code> to SAMR, the sequence of calls is:
|
|||||||
-- |_ |_ Administrator account cannot be locked out
|
-- |_ |_ Administrator account cannot be locked out
|
||||||
--
|
--
|
||||||
-- @args smb* This script supports the <code>smbusername</code>,
|
-- @args smb* This script supports the <code>smbusername</code>,
|
||||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
-- script arguments of the <code>smb</code> module.
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -59,6 +59,7 @@ end
|
|||||||
action = function(host)
|
action = function(host)
|
||||||
local response = " \n"
|
local response = " \n"
|
||||||
local status, smbstate
|
local status, smbstate
|
||||||
|
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)
|
||||||
@@ -107,7 +108,7 @@ action = function(host)
|
|||||||
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['domains'] == 0) then
|
if(#enumdomains_result['sam']['entries'] == 0) then
|
||||||
if(nmap.debugging() > 0) then
|
if(nmap.debugging() > 0) then
|
||||||
return "ERROR: Couldn't find any domains to check"
|
return "ERROR: Couldn't find any domains to check"
|
||||||
else
|
else
|
||||||
@@ -115,9 +116,9 @@ action = function(host)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 1, #enumdomains_result['domains'], 1 do
|
for i = 1, #enumdomains_result['sam']['entries'], 1 do
|
||||||
|
|
||||||
local domain = enumdomains_result['domains'][i]
|
local domain = enumdomains_result['sam']['entries'][i]['name']
|
||||||
local sid
|
local sid
|
||||||
local domain_handle
|
local domain_handle
|
||||||
|
|
||||||
@@ -131,7 +132,6 @@ action = function(host)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Save the sid
|
-- Save the sid
|
||||||
sid = lookupdomain_result['sid']
|
sid = lookupdomain_result['sid']
|
||||||
|
|
||||||
@@ -149,33 +149,32 @@ action = function(host)
|
|||||||
-- Save the domain handle
|
-- Save the domain handle
|
||||||
domain_handle = opendomain_result['domain_handle']
|
domain_handle = opendomain_result['domain_handle']
|
||||||
|
|
||||||
-- Call QueryDomainInfo2() to get domain properties. We call these for three types == 1, 8, and 12, since those return
|
-- Call QueryDomainInfo2() to get domain properties. We call these for three types -- 1, 8, and 12, since those return
|
||||||
-- the most useful information.
|
-- the most useful information.
|
||||||
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 1)
|
status_1, querydomaininfo2_result_1 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 1)
|
||||||
if(status == false) then
|
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)
|
msrpc.stop_smb(smbstate)
|
||||||
if(nmap.debugging() > 0) then
|
if(nmap.debugging() > 0) then
|
||||||
return "ERROR: " .. querydomaininfo2_result
|
return "ERROR: " .. querydomaininfo2_result_1
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if(status_8 == false) then
|
||||||
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 8, querydomaininfo2_result)
|
|
||||||
if(status == false) then
|
|
||||||
msrpc.stop_smb(smbstate)
|
msrpc.stop_smb(smbstate)
|
||||||
if(nmap.debugging() > 0) then
|
if(nmap.debugging() > 0) then
|
||||||
return "ERROR: " .. querydomaininfo2_result
|
return "ERROR: " .. querydomaininfo2_result_8
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if(status_12 == false) then
|
||||||
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 12, querydomaininfo2_result)
|
|
||||||
if(status == false) then
|
|
||||||
msrpc.stop_smb(smbstate)
|
msrpc.stop_smb(smbstate)
|
||||||
if(nmap.debugging() > 0) then
|
if(nmap.debugging() > 0) then
|
||||||
return "ERROR: " .. querydomaininfo2_result
|
return "ERROR: " .. querydomaininfo2_result_12
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
@@ -185,7 +184,7 @@ action = function(host)
|
|||||||
status, enumdomainusers_result = msrpc.samr_enumdomainusers(smbstate, domain_handle)
|
status, enumdomainusers_result = msrpc.samr_enumdomainusers(smbstate, domain_handle)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
msrpc.stop_smb(smbstate)
|
msrpc.stop_smb(smbstate)
|
||||||
if(nmap.debugging() > 0) then
|
if(nmap.debugging() > 0 and enumdomainusers_result ~= nil) then
|
||||||
return "ERROR: " .. enumdomainusers_result
|
return "ERROR: " .. enumdomainusers_result
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
@@ -195,16 +194,30 @@ action = function(host)
|
|||||||
-- Close the domain handle
|
-- Close the domain handle
|
||||||
msrpc.samr_close(smbstate, 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!
|
-- Finally, fill in the response!
|
||||||
response = response .. string.format("Domain: %s\n", domain)
|
response = response .. string.format("Domain: %s\n", domain)
|
||||||
response = response .. string.format(" |_ SID: %s\n", msrpc.sid_to_string(lookupdomain_result['sid']))
|
response = response .. string.format(" |_ SID: %s\n", lookupdomain_result['sid'])
|
||||||
response = response .. string.format(" |_ Users: %s\n", stdnse.strjoin(", ", enumdomainusers_result['names']))
|
if(#names ~= 0) then
|
||||||
response = response .. string.format(" |_ Creation time: %s\n", querydomaininfo2_result['create_date'])
|
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
|
-- Password characteristics
|
||||||
local min_password_length = querydomaininfo2_result['min_password_length']
|
local min_password_length = querydomaininfo2_result_1['info']['min_password_length']
|
||||||
local max_password_age = querydomaininfo2_result['max_password_age']
|
local max_password_age = querydomaininfo2_result_1['info']['max_password_age'] / 60 / 60 / 24
|
||||||
local min_password_age = querydomaininfo2_result['min_password_age']
|
local min_password_age = querydomaininfo2_result_1['info']['min_password_age'] / 60 / 60 / 24
|
||||||
|
|
||||||
if(min_password_length > 0) then
|
if(min_password_length > 0) then
|
||||||
min_password_length = string.format("%d characters", min_password_length)
|
min_password_length = string.format("%d characters", min_password_length)
|
||||||
@@ -226,25 +239,29 @@ action = function(host)
|
|||||||
|
|
||||||
response = response .. string.format(" |_ Passwords: min length: %s; min age: %s; max age: %s\n", min_password_length, min_password_age, max_password_age)
|
response = response .. string.format(" |_ Passwords: min length: %s; min age: %s; max age: %s\n", min_password_length, min_password_age, max_password_age)
|
||||||
|
|
||||||
local lockout_duration = querydomaininfo2_result['lockout_duration']
|
local lockout_duration = querydomaininfo2_result_12['info']['lockout_duration']
|
||||||
if(lockout_duration < 0) then
|
if(lockout_duration < 0) then
|
||||||
lockout_duration = string.format("for %d minutes", querydomaininfo2_result['lockout_duration'])
|
lockout_duration = string.format("for %d minutes", querydomaininfo2_result_12['info']['lockout_duration'])
|
||||||
else
|
else
|
||||||
lockout_duration = "until manually reset"
|
lockout_duration = "until manually reset"
|
||||||
end
|
end
|
||||||
|
|
||||||
if(querydomaininfo2_result['lockout_threshold'] > 0) then
|
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['lockout_threshold'], querydomaininfo2_result['lockout_window'], lockout_duration)
|
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
|
else
|
||||||
response = response .. string.format(" |_ Account lockout disabled\n")
|
response = response .. string.format(" |_ Account lockout disabled\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
if(querydomaininfo2_result['password_history_length']) > 0 then
|
if(querydomaininfo2_result_1['info']['password_history_length']) > 0 then
|
||||||
response = response .. string.format(" |_ Password history : %d passwords\n", querydomaininfo2_result['password_history_length'])
|
response = response .. string.format(" |_ Password history: %d passwords\n", querydomaininfo2_result_1['info']['password_history_length'])
|
||||||
end
|
end
|
||||||
|
|
||||||
if(#querydomaininfo2_result['password_properties_list'] > 0) then
|
local password_properties = querydomaininfo2_result_1['info']['password_properties']
|
||||||
response = response .. " |_ Password properties: \n |_ " .. stdnse.strjoin("\n |_ ", querydomaininfo2_result['password_properties_list']) .. "\n"
|
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
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
description = [[
|
description = [[
|
||||||
Enumerates the users logged into a system either locally, through a remote desktop client (terminal
|
Enumerates the users logged into a system either locally or through an SMB share.
|
||||||
services), or through a SMB share.
|
|
||||||
|
|
||||||
Enumerating the local and terminal services users is done by reading the remote registry. Keys under
|
Enumerating the local and terminal services users is done by reading the remote registry. Keys stored under
|
||||||
<code>HKEY_USERS</code> are SIDs that represent the currently logged in users, and those SIDs can be converted
|
<code>HKEY_USERS</code> are SIDs that represent the currently logged in users, and those SIDs can be converted
|
||||||
to proper names by using the <code>LsaLookupSids</code> function. Doing this requires any access higher than
|
to proper names by using the <code>lsar.LsaLookupSids</code> function. Doing this requires any access higher than
|
||||||
anonymous. Guests, users, or administrators are all able to perform this request on the operating
|
anonymous. Guests, users, or administrators are all able to perform this request on the operating
|
||||||
systems I (Ron Bowes) tested.
|
systems I (Ron Bowes) tested.
|
||||||
|
|
||||||
@@ -19,8 +18,12 @@ Since both of these are related to users being logged into the server, it seemed
|
|||||||
them into a single script.
|
them into a single script.
|
||||||
|
|
||||||
I learned the idea and technique for this from sysinternals' tool, PsLoggedOn.exe. I use similar
|
I learned the idea and technique for this from sysinternals' tool, PsLoggedOn.exe. I use similar
|
||||||
function calls to what they use, so thanks go out to them. Thanks also to Matt, for giving me the
|
function calls to what they use, so thanks go out to them. Thanks also to Matt Gardenghi, for requesting
|
||||||
idea to write this one.
|
this script.
|
||||||
|
|
||||||
|
WARNING: I have experienced crashes in regsvc.exe while making registry calls against a fully patched Windows
|
||||||
|
2000 system; I've fixed the issue that caused it, but there's no guarantee that it (or a similar vuln in the
|
||||||
|
same code) won't show up again.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -38,8 +41,8 @@ idea to write this one.
|
|||||||
-- |_ |_ 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]
|
||||||
--
|
--
|
||||||
-- @args smb* This script supports the <code>smbusername</code>,
|
-- @args smb* This script supports the <code>smbusername</code>,
|
||||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
-- script arguments of the <code>smb</code> module.
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -89,12 +92,15 @@ local function srvsvc_enum_sessions(host)
|
|||||||
-- Stop the SMB session
|
-- Stop the SMB session
|
||||||
msrpc.stop_smb(smbstate)
|
msrpc.stop_smb(smbstate)
|
||||||
|
|
||||||
return true, netsessenum_result['sessions']
|
return true, netsessenum_result['ctr']['array']
|
||||||
end
|
end
|
||||||
|
|
||||||
---Enumerates the users logged in locally (or through terminal services) by using functions
|
---Enumerates the users logged in locally (or through terminal services) by using functions
|
||||||
-- that access the registry. To perform this check, guest access or higher is required.
|
-- that access the registry. To perform this check, guest access or higher is required.
|
||||||
--
|
--
|
||||||
|
-- The way this works is based on the registry. HKEY_USERS is enumerated, and every key in it
|
||||||
|
-- that looks like a SID is converted to a username using the LSA lookup function lsa_lookupsids2().
|
||||||
|
--
|
||||||
--@param host The host object.
|
--@param host The host object.
|
||||||
--@return An array of user tables, each with the keys <code>name</code>, <code>domain</code>, and <code>changed_date</code> (representing
|
--@return An array of user tables, each with the keys <code>name</code>, <code>domain</code>, and <code>changed_date</code> (representing
|
||||||
-- when they logged in).
|
-- when they logged in).
|
||||||
@@ -124,14 +130,13 @@ local function winreg_enum_rids(host)
|
|||||||
-- Loop through the keys under HKEY_USERS and grab the names
|
-- Loop through the keys under HKEY_USERS and grab the names
|
||||||
i = 0
|
i = 0
|
||||||
repeat
|
repeat
|
||||||
status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], i)
|
status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], i, "")
|
||||||
|
|
||||||
if(status == true) then
|
if(status == true) then
|
||||||
local status, openkey_result
|
local status, openkey_result
|
||||||
|
|
||||||
local element = {}
|
local element = {}
|
||||||
element['name'] = enumkey_result['name']
|
element['name'] = enumkey_result['name']
|
||||||
element['sid'] = msrpc.string_to_sid(enumkey_result['name'])
|
|
||||||
|
|
||||||
-- To get the time the user logged in, we check the 'Volatile Environment' key
|
-- To get the time the user logged in, we check the 'Volatile Environment' key
|
||||||
-- This can fail with the 'guest' account due to access restrictions
|
-- This can fail with the 'guest' account due to access restrictions
|
||||||
@@ -191,48 +196,46 @@ local function winreg_enum_rids(host)
|
|||||||
return false, openpolicy2_result
|
return false, openpolicy2_result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Convert the RIDs to names
|
-- Convert the SID to the name of the user
|
||||||
local results = {}
|
local results = {}
|
||||||
stdnse.print_debug(3, "MSRPC: Found %d SIDs that might be logged in", #elements)
|
stdnse.print_debug(3, "MSRPC: Found %d SIDs that might be logged in", #elements)
|
||||||
for i = 1, #elements, 1 do
|
for i = 1, #elements, 1 do
|
||||||
if(elements[i]['sid'] ~= nil) then
|
if(elements[i]['name'] ~= nil) then
|
||||||
-- The RID is the last subauthority
|
local sid = elements[i]['name']
|
||||||
local rid = elements[i]['sid']['subauthorities'][elements[i]['sid']['count']]
|
if(string.find(sid, "^S-") ~= nil and string.find(sid, "-%d+$") ~= nil) then
|
||||||
stdnse.print_debug(3, "MSRPC: Found an actual RID: %d", rid)
|
-- The rid is the last digits before the end of the string
|
||||||
|
local rid = string.sub(sid, string.find(sid, "%d+$"))
|
||||||
|
|
||||||
-- The server is the rest of the SID, so remove the last subauthority
|
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], {elements[i]['name']})
|
||||||
elements[i]['sid']['subauthorities'][elements[i]['sid']['count']] = nil
|
|
||||||
elements[i]['sid']['count'] = elements[i]['sid']['count'] - 1
|
|
||||||
|
|
||||||
-- Look up the RID
|
|
||||||
stdnse.print_debug(3, "MSRPC: Looking up RID %s in SID %s", rid, msrpc.sid_to_string(elements[i]['sid']))
|
|
||||||
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], elements[i]['sid'], {rid})
|
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
-- It may not succeed, if it doesn't that's ok
|
-- It may not succeed, if it doesn't that's ok
|
||||||
stdnse.print_debug(3, "MSRPC: Lookup failed")
|
stdnse.print_debug(3, "MSRPC: Lookup failed")
|
||||||
else
|
else
|
||||||
-- Create the result array
|
-- Create the result array
|
||||||
local result = {}
|
local result = {}
|
||||||
result['rid'] = rid
|
|
||||||
result['changed_date'] = elements[i]['changed_date']
|
result['changed_date'] = elements[i]['changed_date']
|
||||||
|
result['rid'] = rid
|
||||||
|
|
||||||
-- Fill in the result from the response
|
-- Fill in the result from the response
|
||||||
if(lookupsids2_result['details'][1] == nil) then
|
if(lookupsids2_result['names']['names'][1] == nil) then
|
||||||
result['name'] = "<unknown>"
|
result['name'] = "<unknown>"
|
||||||
|
result['type'] = "<unknown>"
|
||||||
result['domain'] = ""
|
result['domain'] = ""
|
||||||
else
|
else
|
||||||
result['name'] = lookupsids2_result['details'][1]['name']
|
result['name'] = lookupsids2_result['names']['names'][1]['name']
|
||||||
result['type'] = lookupsids2_result['details'][1]['type']
|
result['type'] = lookupsids2_result['names']['names'][1]['sid_type']
|
||||||
result['domain'] = lookupsids2_result['domains'][1]['name']
|
result['domain'] = lookupsids2_result['domains']['domains'][1]['name']
|
||||||
end
|
end
|
||||||
|
|
||||||
if(result['type'] ~= 5) then -- Don't show "well known" accounts
|
if(result['type'] ~= "SID_NAME_WKN_GRP") then -- Don't show "well known" accounts
|
||||||
-- Add it to the results
|
-- Add it to the results
|
||||||
results[#results + 1] = result
|
results[#results + 1] = result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Close the policy
|
-- Close the policy
|
||||||
msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle'])
|
msrpc.lsa_close(smbstate, openpolicy2_result['policy_handle'])
|
||||||
@@ -275,29 +278,29 @@ action = function(host)
|
|||||||
-- Format the result
|
-- Format the result
|
||||||
for i = 1, #sessions, 1 do
|
for i = 1, #sessions, 1 do
|
||||||
|
|
||||||
local active = sessions[i]['active']
|
local time = sessions[i]['time']
|
||||||
if(active == 0) then
|
if(time == 0) then
|
||||||
active = "[just logged in, it's probably you]"
|
time = "[just logged in, it's probably you]"
|
||||||
elseif(active > 60 * 60 * 24) then
|
elseif(time > 60 * 60 * 24) then
|
||||||
active = string.format("%dd%dh%02dm%02ds", active / (60*60*24), (active % (60*60*24)) / 3600, (active % 3600) / 60, active % 60)
|
time = string.format("%dd%dh%02dm%02ds", time / (60*60*24), (time % (60*60*24)) / 3600, (time % 3600) / 60, time % 60)
|
||||||
elseif(active > 60 * 60) then
|
elseif(time > 60 * 60) then
|
||||||
active = string.format("%dh%02dm%02ds", active / 3600, (active % 3600) / 60, active % 60)
|
time = string.format("%dh%02dm%02ds", time / 3600, (time % 3600) / 60, time % 60)
|
||||||
else
|
else
|
||||||
active = string.format("%02dm%02ds", active / 60, active % 60)
|
time = string.format("%02dm%02ds", time / 60, time % 60)
|
||||||
end
|
end
|
||||||
|
|
||||||
local idle = sessions[i]['idle']
|
local idle_time = sessions[i]['idle_time']
|
||||||
if(idle == 0) then
|
if(idle_time == 0) then
|
||||||
idle = "[not idle]"
|
idle_time = "[not idle]"
|
||||||
elseif(idle > 60 * 60 * 24) then
|
elseif(idle_time > 60 * 60 * 24) then
|
||||||
idle = string.format("%dd%dh%02dm%02ds", idle / (60*60*24), (idle % (60*60*24)) / 3600, (idle % 3600) / 60, idle % 60)
|
idle_time = string.format("%dd%dh%02dm%02ds", idle_time / (60*60*24), (idle_time % (60*60*24)) / 3600, (idle_time % 3600) / 60, idle_time % 60)
|
||||||
elseif(idle > 60 * 60) then
|
elseif(idle_time > 60 * 60) then
|
||||||
idle = string.format("%dh%02dm%02ds", idle / 3600, (idle % 3600) / 60, idle % 60)
|
idle_time = string.format("%dh%02dm%02ds", idle_time / 3600, (idle_time % 3600) / 60, idle_time % 60)
|
||||||
else
|
else
|
||||||
idle = string.format("%02dm%02ds", idle / 60, idle % 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'], active, idle)
|
response = response .. string.format("|_ %s is connected from %s for %s, idle for %s\n", sessions[i]['user'], sessions[i]['client'], time, idle_time)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
description = [[
|
description = [[
|
||||||
Attempts to list shares using the <code>srvsvc.NetShareEnumAll</code> MSRPC function, then
|
Attempts to list shares using the <code>srvsvc.NetShareEnumAll</code> MSRPC function and
|
||||||
retrieve more information about each share using <code>srvsvc.NetShareGetInfo</code>.
|
retrieve more information about them using <code>srvsvc.NetShareGetInfo</code>.
|
||||||
|
|
||||||
Running
|
Running <code>NetShareEnumAll</code> will work anonymously against Windows 2000, and
|
||||||
<code>NetShareEnumAll</code> will work anonymously on Windows 2000, and requires a user-level
|
requires a user-level account on any other Windows version. Calling <code>NetShareGetInfo</code>
|
||||||
account on any other Windows version. Calling <code>NetShareGetInfo</code> requires an
|
requires an administrator account on every version of Windows I (Ron Bowes) tested.
|
||||||
administrator account on every version of Windows I (Ron Bowes) tested.
|
|
||||||
|
|
||||||
Although <code>NetShareEnumAll</code> is restricted on certain systems, actually connecting to
|
Although <code>NetShareEnumAll</code> is restricted on certain systems, making a connection to
|
||||||
a share to check if it exists will always work. So, if <code>NetShareEnumAll</code> fails, a
|
a share to check whether or not it exists will always work. So, if <code>NetShareEnumAll</code>
|
||||||
list of common shares will be attempted.
|
fails, a list of common shares will be checked.
|
||||||
|
|
||||||
After a list of shares is found, whether or not it's complete, we attempt to connect
|
After a list of shares is found, we attempt to connect to each of them anonymously, which lets
|
||||||
to each of them anonymously, which lets us divide them into the classes
|
us divide them into the classes "anonymous" and "restricted."
|
||||||
"anonymous" and "restricted."
|
|
||||||
|
|
||||||
When possible, once the list of shares is determined, <code>NetShareGetInfo</code> is called
|
When possible, once the list of shares is determined, <code>NetShareGetInfo</code> is called
|
||||||
to get additional information on the share. Odds are this will fail, unless we're
|
to get additional information on them. Odds are this will fail, unless we're doing an authenticated
|
||||||
doing an authenticated test.
|
test.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -58,8 +56,8 @@ doing an authenticated test.
|
|||||||
-- |_ |_ Path: C:\
|
-- |_ |_ Path: C:\
|
||||||
--
|
--
|
||||||
-- @args smb* This script supports the <code>smbusername</code>,
|
-- @args smb* This script supports the <code>smbusername</code>,
|
||||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
-- script arguments of the <code>smb</code> module.
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -85,6 +83,8 @@ local function samr_enum_shares(host)
|
|||||||
|
|
||||||
local status, smbstate
|
local status, smbstate
|
||||||
local bind_result, netshareenumall_result
|
local bind_result, netshareenumall_result
|
||||||
|
local shares
|
||||||
|
local i, v
|
||||||
|
|
||||||
-- Create the SMB session
|
-- Create the SMB session
|
||||||
status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
|
status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
|
||||||
@@ -109,7 +109,13 @@ local function samr_enum_shares(host)
|
|||||||
-- Stop the SMB session
|
-- Stop the SMB session
|
||||||
smb.stop(smbstate)
|
smb.stop(smbstate)
|
||||||
|
|
||||||
return true, netshareenumall_result['shares']
|
-- Convert the share list to an array
|
||||||
|
shares = {}
|
||||||
|
for i, v in pairs(netshareenumall_result['ctr']['array']) do
|
||||||
|
shares[#shares + 1] = v['name']
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, shares
|
||||||
end
|
end
|
||||||
|
|
||||||
---Attempts to connect to a list of shares as the anonymous user, returning which ones
|
---Attempts to connect to a list of shares as the anonymous user, returning which ones
|
||||||
@@ -162,7 +168,7 @@ function check_shares(host, shares)
|
|||||||
stdnse.print_debug(3, "EnumShares: Access was denied")
|
stdnse.print_debug(3, "EnumShares: Access was denied")
|
||||||
denied_shares[#denied_shares + 1] = shares[i]
|
denied_shares[#denied_shares + 1] = shares[i]
|
||||||
else
|
else
|
||||||
stdnse.print_debug(3, "EnumShares: Share didn't pan out: %s", err)
|
stdnse.print_debug(3, "ERROR: EnumShares: Share didn't pan out: %s", err)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Add it to allowed shares
|
-- Add it to allowed shares
|
||||||
@@ -227,11 +233,12 @@ action = function(host)
|
|||||||
-- best we can do.
|
-- best we can do.
|
||||||
if(result == false) then
|
if(result == false) then
|
||||||
if(nmap.debugging() > 0) then
|
if(nmap.debugging() > 0) then
|
||||||
response = response .. string.format("Couldn't enum all shares, checking for common ones (%s)\n", shares)
|
response = response .. string.format("ERROR: Couldn't enum all shares, checking for common ones (%s)\n", shares)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Take some common share names I've seen
|
-- Take some common share names I've seen
|
||||||
shares = {"IPC$", "ADMIN$", "TEST", "TEST$", "HOME", "HOME$"}
|
shares = {"IPC$", "ADMIN$", "TEST", "TEST$", "HOME", "HOME$", "PORN", "PR0N", "PUBLIC", "PRINT", "PRINT$", "GROUPS", "USERS", "MEDIA", "SOFTWARE", "XSERVE", "NETLOGON", "INFO", "PROGRAMS", "FILES", "WWW", "STMP", "TMP", "DATA", "BACKUP", "DOCS", "HD", "WEBSERVER", "WEB DOCUMENTS", "SHARED"}
|
||||||
|
|
||||||
-- Try every alphabetic share, with and without a trailing '$'
|
-- Try every alphabetic share, with and without a trailing '$'
|
||||||
for i = string.byte("A", 1), string.byte("Z", 1), 1 do
|
for i = string.byte("A", 1), string.byte("Z", 1), 1 do
|
||||||
shares[#shares + 1] = string.char(i)
|
shares[#shares + 1] = string.char(i)
|
||||||
@@ -260,13 +267,15 @@ action = function(host)
|
|||||||
response = response .. string.format(" %s\n", allowed[i])
|
response = response .. string.format(" %s\n", allowed[i])
|
||||||
|
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
stdnse.print_debug(2, "Error getting information for share %s: %s", allowed[i], info)
|
stdnse.print_debug(2, "ERROR: Couldn't get information for share %s: %s", allowed[i], info)
|
||||||
else
|
else
|
||||||
|
info = info['info']
|
||||||
|
|
||||||
if(info['max_users'] == 0xFFFFFFFF) then
|
if(info['max_users'] == 0xFFFFFFFF) then
|
||||||
info['max_users'] = "<unlimited>"
|
info['max_users'] = "<unlimited>"
|
||||||
end
|
end
|
||||||
|
|
||||||
response = response .. string.format(" |_ Type: %s\n", info['strtype'])
|
response = response .. string.format(" |_ Type: %s\n", msrpc.srvsvc_ShareType_tostr(info['sharetype']))
|
||||||
response = response .. string.format(" |_ Comment: %s\n", info['comment'])
|
response = response .. string.format(" |_ Comment: %s\n", info['comment'])
|
||||||
response = response .. string.format(" |_ Users: %s, Max: %s\n", info['current_users'], info['max_users'])
|
response = response .. string.format(" |_ Users: %s, Max: %s\n", info['current_users'], info['max_users'])
|
||||||
response = response .. string.format(" |_ Path: %s\n", info['path'])
|
response = response .. string.format(" |_ Path: %s\n", info['path'])
|
||||||
@@ -280,13 +289,14 @@ action = function(host)
|
|||||||
response = response .. string.format(" %s\n", denied[i])
|
response = response .. string.format(" %s\n", denied[i])
|
||||||
|
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
stdnse.print_debug(2, "Error getting information for share %s: %s", denied[i], info)
|
stdnse.print_debug(2, "ERROR: Couldn't get information for share %s: %s", denied[i], info)
|
||||||
else
|
else
|
||||||
|
info = info['info']
|
||||||
if(info['max_users'] == 0xFFFFFFFF) then
|
if(info['max_users'] == 0xFFFFFFFF) then
|
||||||
info['max_users'] = "<unlimited>"
|
info['max_users'] = "<unlimited>"
|
||||||
end
|
end
|
||||||
|
|
||||||
response = response .. string.format(" |_ Type: %s\n", info['strtype'])
|
response = response .. string.format(" |_ Type: %s\n", msrpc.srvsvc_ShareType_tostr(info['sharetype']))
|
||||||
response = response .. string.format(" |_ Comment: %s\n", info['comment'])
|
response = response .. string.format(" |_ Comment: %s\n", info['comment'])
|
||||||
response = response .. string.format(" |_ Users: %s, Max: %s\n", info['current_users'], info['max_users'])
|
response = response .. string.format(" |_ Users: %s, Max: %s\n", info['current_users'], info['max_users'])
|
||||||
response = response .. string.format(" |_ Path: %s\n", info['path'])
|
response = response .. string.format(" |_ Path: %s\n", info['path'])
|
||||||
|
|||||||
@@ -1,13 +1,34 @@
|
|||||||
description = [[
|
description = [[
|
||||||
Attempts to enumerate the users on a remote Windows system, with as much
|
Attempts to enumerate the users on a remote Windows system, with as much
|
||||||
information as possible, through a variety of techniques (over SMB and MSRPC,
|
information as possible, through two different techniques (both over MSRPC,
|
||||||
which uses port 445 or 139). Some functions in SAMR are used to enumerate
|
which uses port 445 or 139). Some SAMR functions are used to enumerate users,
|
||||||
users, and some brute-force guessing using LSA functions is attempted.
|
and bruteforce LSA guessing is attempted.
|
||||||
|
|
||||||
One technique used is calling the <code>QueryDisplayInfo</code> function in the SAMR library.
|
By default, both SAMR enumeration and LSA bruteforcing are used; however, these
|
||||||
If this succeeds, it will return a detailed list of users. This can be done
|
can be fine tuned using Nmap parameters. For the most possible information,
|
||||||
anonymously against Windows 2000, and with a user-level account on other Windows
|
leave the defaults; however, there are advantages to using them individually.
|
||||||
versions (but not with a guest-level account).
|
|
||||||
|
Advantages of using SAMR enumeration:
|
||||||
|
* Stealthier (requires one packet/user account, whereas LSA uses at least 20
|
||||||
|
packets; additionally, LSA makes a lot of noise in the Windows event log (LSA
|
||||||
|
enumeration is the only script I (Ron Bowes) have been called on by the
|
||||||
|
administrator of a box I was testing against).
|
||||||
|
* More information is returned (more than just the username).
|
||||||
|
* Every account will be found, since they're being enumerated with a function
|
||||||
|
that's designed to enumerate users.
|
||||||
|
|
||||||
|
Advantages of using LSA bruteforcing:
|
||||||
|
* More accounts are returned (system accounts, groups, and aliases are returned,
|
||||||
|
not just users).
|
||||||
|
* Requires a lower-level account to run on Windows XP and higher (a 'guest' account
|
||||||
|
can be used, whereas SAMR enumeration requires a 'user' account; especially useful
|
||||||
|
when only guest access is allowed, or when an account has a blank password (which
|
||||||
|
effectively gives it guest access)).
|
||||||
|
|
||||||
|
SAMR enumeration is done with the <code>QueryDisplayInfo</code> function.
|
||||||
|
If this succeeds, it will return a detailed list of users, along with descriptions,
|
||||||
|
types, and full names. This can be done anonymously against Windows 2000, and
|
||||||
|
with a user-level account on other Windows versions (but not with a guest-level account).
|
||||||
|
|
||||||
To perform this test, the following functions are used:
|
To perform this test, the following functions are used:
|
||||||
* <code>Bind</code>: bind to the SAMR service.
|
* <code>Bind</code>: bind to the SAMR service.
|
||||||
@@ -29,7 +50,7 @@ against Windows 2000, and requires a guest account or better on other systems.
|
|||||||
It has the advantage of running with less permission, and will also find more
|
It has the advantage of running with less permission, and will also find more
|
||||||
account types (i.e., groups, aliases, etc.). The disadvantages is that it returns
|
account types (i.e., groups, aliases, etc.). The disadvantages is that it returns
|
||||||
less information, and that, because it's a brute-force guess, it's possible to miss
|
less information, and that, because it's a brute-force guess, it's possible to miss
|
||||||
accounts.
|
accounts. It's also extremely noisy.
|
||||||
|
|
||||||
This isn't a brute-force technique in the common sense, however: it's a brute-forcing of users'
|
This isn't a brute-force technique in the common sense, however: it's a brute-forcing of users'
|
||||||
RIDs. A user's RID is a value (generally 500, 501, or 1000+) that uniquely identifies
|
RIDs. A user's RID is a value (generally 500, 501, or 1000+) that uniquely identifies
|
||||||
@@ -37,26 +58,28 @@ a user on a domain or system. An LSA function is exposed which lets us convert t
|
|||||||
(say, 1000) to the username (say, "Ron"). So, the technique will essentially try
|
(say, 1000) to the username (say, "Ron"). So, the technique will essentially try
|
||||||
converting 1000 to a name, then 1001, 1002, etc., until we think we're done.
|
converting 1000 to a name, then 1001, 1002, etc., until we think we're done.
|
||||||
|
|
||||||
Users are broken into groups of five RIDs, then checked individually (checking too many
|
To do this, this script breaks users into groups of five RIDs, then checked individually
|
||||||
at once causes problems). We continue checking until we reach 1100, and get an empty
|
(checking too many at once causes problems). We continue checking until we reach
|
||||||
group. This probably isn't the most effective way, but it seems to work.
|
1100, and get an empty group of five. This probably isn't the most effective way, but it
|
||||||
It might be a good idea to modify this, in the future, with some more
|
seems to work. It might be a good idea to modify this, in the future, with some more
|
||||||
intelligence. I (Ron Bowes) performed a test on an old server with a lot of accounts,
|
intelligence. I (Ron Bowes) performed a test on an old server with a lot of accounts,
|
||||||
and I got these results: 500, 501, 1000, 1030, 1031, 1053, 1054, 1055,
|
and these were the active RIDs: 500, 501, 1000, 1030, 1031, 1053, 1054, 1055,
|
||||||
1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1070,
|
1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1070,
|
||||||
1075, 1081, 1088, 1090. The jump from 1000 to 1030 is quite large and can easily
|
1075, 1081, 1088, 1090. The jump from 1000 to 1030 is quite large and can easily
|
||||||
result in missing accounts, in an automated check.
|
result in missing accounts, in an automated check. An ideal solution might be to continue
|
||||||
|
doing groups of 5, but wait until we get 5-10 consecutive empty groups before giving up.
|
||||||
|
|
||||||
Before attempting this conversion, the SID of the server has to be determined.
|
Before attempting this conversion, the SID of the server has to be determined.
|
||||||
The SID is determined by doing the reverse operation, that is, converting a name into
|
The SID is determined by doing the reverse operation; that is, by converting a name into
|
||||||
a RID. The name is determined by looking up any name present on the system.
|
its RID. The name is determined by looking up any name present on the system.
|
||||||
We try:
|
We try:
|
||||||
* The computer name and domain name, returned in <code>SMB_COM_NEGOTIATE</code>;
|
* The computer name and domain name, returned in <code>SMB_COM_NEGOTIATE</code>;
|
||||||
* An nbstat query to get the server name and the user currently logged in; and
|
* An nbstat query to get the server name and the user currently logged in; and
|
||||||
* Some common names: "administrator", "guest", and "test".
|
* Some common names: "administrator", "guest", and "test".
|
||||||
|
|
||||||
In theory, the computer name should be sufficient for this to always work, and
|
In theory, the computer name should be sufficient for this to always work, and
|
||||||
so far has in my tests, but I included the rest of the names for good measure.
|
it has so far has in my tests, but I included the rest of the names for good measure. It
|
||||||
|
doesn't hurt to add more.
|
||||||
|
|
||||||
The names and details from both of these techniques are merged and displayed.
|
The names and details from both of these techniques are merged and displayed.
|
||||||
If the output is verbose, then extra details are shown. The output is ordered alphabetically.
|
If the output is verbose, then extra details are shown. The output is ordered alphabetically.
|
||||||
@@ -110,8 +133,14 @@ the code I wrote for this is largely based on the techniques used by them.
|
|||||||
-- |_ |_ Domain: LOCALSYSTEM
|
-- |_ |_ Domain: LOCALSYSTEM
|
||||||
--
|
--
|
||||||
-- @args smb* This script supports the <code>smbusername</code>,
|
-- @args smb* This script supports the <code>smbusername</code>,
|
||||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
-- script arguments of the <code>smb</code> module.
|
||||||
|
-- @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
|
||||||
|
-- by using the default options.
|
||||||
|
-- @args samronly If set, script will only query a list of users using a SAMR lookup. This is
|
||||||
|
-- much quieter than LSA lookups, so enable this if you want stealth. Generally, however,
|
||||||
|
-- you'll get better results by using the default options.
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -136,6 +165,7 @@ end
|
|||||||
--<code>domain</code>, <code>fullname</code>, <code>rid</code>, and
|
--<code>domain</code>, <code>fullname</code>, <code>rid</code>, and
|
||||||
--<code>description</code>.
|
--<code>description</code>.
|
||||||
local function enum_samr(host)
|
local function enum_samr(host)
|
||||||
|
local i, j
|
||||||
|
|
||||||
stdnse.print_debug(3, "Entering enum_samr()")
|
stdnse.print_debug(3, "Entering enum_samr()")
|
||||||
|
|
||||||
@@ -177,15 +207,15 @@ local function enum_samr(host)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- If no domains were returned, go back with an error
|
-- If no domains were returned, go back with an error
|
||||||
if(#enumdomains_result['domains'] == 0) then
|
if(#enumdomains_result['sam']['entries'] == 0) then
|
||||||
msrpc.stop_smb(smbstate)
|
msrpc.stop_smb(smbstate)
|
||||||
return false, "Couldn't find any domains"
|
return false, "Couldn't find any domains"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Now, loop through the domains and find the users
|
-- Now, loop through the domains and find the users
|
||||||
for i = 1, #enumdomains_result['domains'], 1 do
|
for i = 1, #enumdomains_result['sam']['entries'], 1 do
|
||||||
|
|
||||||
local domain = enumdomains_result['domains'][i]
|
local domain = enumdomains_result['sam']['entries'][i]['name']
|
||||||
-- We don't care about the 'builtin' domain, in all my tests it's empty
|
-- We don't care about the 'builtin' domain, in all my tests it's empty
|
||||||
if(domain ~= 'Builtin') then
|
if(domain ~= 'Builtin') then
|
||||||
local sid
|
local sid
|
||||||
@@ -212,24 +242,52 @@ local function enum_samr(host)
|
|||||||
-- Save the domain handle
|
-- Save the domain handle
|
||||||
domain_handle = opendomain_result['domain_handle']
|
domain_handle = opendomain_result['domain_handle']
|
||||||
|
|
||||||
|
-- Loop as long as we're getting valid results
|
||||||
|
j = 0
|
||||||
|
repeat
|
||||||
-- Call QueryDisplayInfo()
|
-- Call QueryDisplayInfo()
|
||||||
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle)
|
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle, j)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
msrpc.stop_smb(smbstate)
|
msrpc.stop_smb(smbstate)
|
||||||
return false, querydisplayinfo_result
|
return false, querydisplayinfo_result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Save the response
|
||||||
|
if(querydisplayinfo_result['return'] ~= 0 and querydisplayinfo_result['info'] ~= nil and querydisplayinfo_result['info']['entries'] ~= nil and querydisplayinfo_result['info']['entries'][1] ~= nil) then
|
||||||
|
local array = {}
|
||||||
|
local k
|
||||||
|
|
||||||
|
-- The reason these are all indexed from '1' is because we request names one at a time.
|
||||||
|
array['domain'] = domain
|
||||||
|
array['name'] = querydisplayinfo_result['info']['entries'][1]['account_name']
|
||||||
|
array['fullname'] = querydisplayinfo_result['info']['entries'][1]['full_name']
|
||||||
|
array['description'] = querydisplayinfo_result['info']['entries'][1]['description']
|
||||||
|
array['rid'] = querydisplayinfo_result['info']['entries'][1]['rid']
|
||||||
|
array['flags'] = querydisplayinfo_result['info']['entries'][1]['acct_flags']
|
||||||
|
array['source'] = "SAMR Enumeration"
|
||||||
|
|
||||||
|
-- Clean up the 'flags' array
|
||||||
|
for k = 1, #array['flags'], 1 do
|
||||||
|
array['flags'][k] = msrpc.samr_AcctFlags_tostr(array['flags'][k])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add it to the array
|
||||||
|
response[#response + 1] = array
|
||||||
|
end
|
||||||
|
j = j + 1
|
||||||
|
until querydisplayinfo_result['return'] == 0
|
||||||
|
|
||||||
-- Close the domain handle
|
-- Close the domain handle
|
||||||
msrpc.samr_close(smbstate, domain_handle)
|
msrpc.samr_close(smbstate, domain_handle)
|
||||||
|
|
||||||
-- Finally, fill in the response!
|
-- Finally, fill in the response!
|
||||||
for i = 1, #querydisplayinfo_result['details'], 1 do
|
-- for i = 1, #querydisplayinfo_result['details'], 1 do
|
||||||
querydisplayinfo_result['details'][i]['domain'] = domain
|
-- querydisplayinfo_result['details'][i]['domain'] = domain
|
||||||
-- All we get from this is users
|
-- -- All we get from this is users
|
||||||
querydisplayinfo_result['details'][i]['typestr'] = "User"
|
-- querydisplayinfo_result['details'][i]['typestr'] = "User"
|
||||||
querydisplayinfo_result['details'][i]['source'] = "SAMR Enumeration"
|
-- querydisplayinfo_result['details'][i]['source'] = "SAMR Enumeration"
|
||||||
response[#response + 1] = querydisplayinfo_result['details'][i]
|
-- response[#response + 1] = querydisplayinfo_result['details'][i]
|
||||||
end
|
-- end
|
||||||
end -- Checking for 'builtin'
|
end -- Checking for 'builtin'
|
||||||
end -- Domain loop
|
end -- Domain loop
|
||||||
|
|
||||||
@@ -301,31 +359,30 @@ local function enum_lsa(host)
|
|||||||
msrpc.stop_smb(smbstate)
|
msrpc.stop_smb(smbstate)
|
||||||
return false, lookupnames2_result
|
return false, lookupnames2_result
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Loop through the domains returned and find the users in each
|
-- Loop through the domains returned and find the users in each
|
||||||
for i = 1, #lookupnames2_result['domains'], 1 do
|
for i = 1, #lookupnames2_result['domains']['domains'], 1 do
|
||||||
local domain = lookupnames2_result['domains'][i]['name']
|
local domain = lookupnames2_result['domains']['domains'][i]['name']
|
||||||
local sid = lookupnames2_result['domains'][i]['sid']
|
local sid = lookupnames2_result['domains']['domains'][i]['sid']
|
||||||
local rids = { }
|
local sids = { }
|
||||||
local start = 1000
|
local start = 1000
|
||||||
|
|
||||||
-- Start by looking up 500 - 505 (will likely be Administrator + guest)
|
-- Start by looking up 500 - 505 (will likely be Administrator + guest)
|
||||||
for j = 500, 505, 1 do
|
for j = 500, 505, 1 do
|
||||||
rids[#rids + 1] = j
|
sids[#sids + 1] = sid .. "-" .. j
|
||||||
end
|
end
|
||||||
|
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids)
|
||||||
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sid, rids)
|
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
|
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
|
||||||
else
|
else
|
||||||
-- Put the details for each name into an array
|
-- Put the details for each name into an array
|
||||||
for j = 1, #lookupsids2_result['details'], 1 do
|
-- NOTE: Be sure to mirror any changes here in the next bit!
|
||||||
if(lookupsids2_result['details'][j]['type'] ~= 8) then -- 8 = user not found
|
for j = 1, #lookupsids2_result['names']['names'], 1 do
|
||||||
|
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
|
||||||
local result = {}
|
local result = {}
|
||||||
result['name'] = lookupsids2_result['details'][j]['name']
|
result['name'] = lookupsids2_result['names']['names'][j]['name']
|
||||||
result['rid'] = 500 + j - 1
|
result['rid'] = 500 + j - 1
|
||||||
result['domain'] = domain
|
result['domain'] = domain
|
||||||
result['typestr'] = lookupsids2_result['details'][j]['typestr']
|
result['typestr'] = msrpc.lsa_SidType_tostr(lookupsids2_result['names']['names'][j]['sid_type'])
|
||||||
result['source'] = "LSA Bruteforce"
|
result['source'] = "LSA Bruteforce"
|
||||||
response[#response + 1] = result
|
response[#response + 1] = result
|
||||||
end
|
end
|
||||||
@@ -335,29 +392,26 @@ local function enum_lsa(host)
|
|||||||
-- Now do groups of 5 users, until we get past 1100 and have an empty group
|
-- Now do groups of 5 users, until we get past 1100 and have an empty group
|
||||||
repeat
|
repeat
|
||||||
local used_names = 0
|
local used_names = 0
|
||||||
local rids = {}
|
local sids = {}
|
||||||
for j = start, start + 4, 1 do
|
for j = start, start + 4, 1 do
|
||||||
rids[#rids + 1] = j
|
sids[#sids + 1] = sid .. "-" .. j
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Try converting this group of RIDs into names
|
-- Try converting this group of RIDs into names
|
||||||
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sid, rids)
|
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids)
|
||||||
if(status == false) then
|
if(status == false) then
|
||||||
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
|
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
|
||||||
else
|
else
|
||||||
-- Put the details for each name into an array
|
-- Put the details for each name into an array
|
||||||
for j = 1, #lookupsids2_result['details'], 1 do
|
for j = 1, #lookupsids2_result['names']['names'], 1 do
|
||||||
if(lookupsids2_result['details'][j]['type'] ~= 8) then -- 8 = user not found
|
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
|
||||||
local result = {}
|
local result = {}
|
||||||
result['name'] = lookupsids2_result['details'][j]['name']
|
result['name'] = lookupsids2_result['names']['names'][j]['name']
|
||||||
result['rid'] = start + j - 1
|
result['rid'] = start + j - 1
|
||||||
result['domain'] = domain
|
result['domain'] = domain
|
||||||
result['typestr'] = lookupsids2_result['details'][j]['typestr']
|
result['typestr'] = msrpc.lsa_SidType_tostr(lookupsids2_result['names']['names'][j]['sid_type'])
|
||||||
result['source'] = "LSA Bruteforce"
|
result['source'] = "LSA Bruteforce"
|
||||||
response[#response + 1] = result
|
response[#response + 1] = result
|
||||||
|
|
||||||
-- Increment the number of used names we have
|
|
||||||
used_names = used_names + 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -381,32 +435,43 @@ end
|
|||||||
|
|
||||||
action = function(host)
|
action = function(host)
|
||||||
local i, j
|
local i, j
|
||||||
local samr_status, lsa_status
|
local samr_status = false
|
||||||
local samr_result, lsa_result
|
local lsa_status = false
|
||||||
|
local samr_result = "Didn't run"
|
||||||
|
local lsa_result = "Didn't run"
|
||||||
local names = {}
|
local names = {}
|
||||||
local name_strings = {}
|
local name_strings = {}
|
||||||
local response = " \n"
|
local response = " \n"
|
||||||
|
local samronly = nmap.registry.args.samronly
|
||||||
|
local lsaonly = nmap.registry.args.lsaonly
|
||||||
|
local do_samr = samronly ~= 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 LSA first. Since LSA provides less information, we want the
|
||||||
-- SAMR result to overwrite it.
|
-- SAMR result to overwrite it.
|
||||||
|
if(do_lsa) then
|
||||||
lsa_status, lsa_result = enum_lsa(host)
|
lsa_status, lsa_result = enum_lsa(host)
|
||||||
if(lsa_status == false) then
|
if(lsa_status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
if(nmap.debugging() > 0) then
|
||||||
response = response .. "ERROR: couldn't enum through LSA: " .. lsa_result .. "\n"
|
response = response .. "ERROR: Couldn't enumerate through LSA: " .. lsa_result .. "\n"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Copy the returned array into the names[] table, using the name as the key
|
-- Copy the returned array into the names[] table, using the name as the key
|
||||||
stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
|
stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
|
||||||
for i = 1, #lsa_result, 1 do
|
for i = 1, #lsa_result, 1 do
|
||||||
|
if(lsa_result[i]['name'] ~= nil) then
|
||||||
names[string.upper(lsa_result[i]['name'])] = lsa_result[i]
|
names[string.upper(lsa_result[i]['name'])] = lsa_result[i]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Try enumerating through SAMR
|
-- Try enumerating through SAMR
|
||||||
|
if(do_samr) then
|
||||||
samr_status, samr_result = enum_samr(host)
|
samr_status, samr_result = enum_samr(host)
|
||||||
if(samr_status == false) then
|
if(samr_status == false) then
|
||||||
if(nmap.debugging() > 0) then
|
if(nmap.debugging() > 0) then
|
||||||
response = response .. "ERROR: couldn't enumerate through SAMR: " .. samr_result .. "\n"
|
response = response .. "ERROR: Couldn't enumerate through SAMR: " .. samr_result .. "\n"
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
-- Copy the returned array into the names[] table, using the name as the key
|
-- Copy the returned array into the names[] table, using the name as the key
|
||||||
@@ -415,6 +480,7 @@ action = function(host)
|
|||||||
names[string.upper(samr_result[i]['name'])] = samr_result[i]
|
names[string.upper(samr_result[i]['name'])] = samr_result[i]
|
||||||
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
|
||||||
@@ -434,7 +500,7 @@ action = function(host)
|
|||||||
|
|
||||||
-- Check if we actually got any names back
|
-- Check if we actually got any names back
|
||||||
if(#name_strings == 0) then
|
if(#name_strings == 0) then
|
||||||
response = response .. "Sorry, couldn't find any account names anonymously!"
|
response = response .. "Couldn't find any account names anonymously, 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
|
||||||
@@ -457,7 +523,7 @@ action = function(host)
|
|||||||
end
|
end
|
||||||
if(names[name]['fullname'] ~= nil) then response = response .. string.format(" |_ Full name: %s\n", names[name]['fullname']) end
|
if(names[name]['fullname'] ~= nil) then response = response .. string.format(" |_ Full name: %s\n", names[name]['fullname']) end
|
||||||
if(names[name]['description'] ~= nil) then response = response .. string.format(" |_ Description: %s\n", names[name]['description']) end
|
if(names[name]['description'] ~= nil) then response = response .. string.format(" |_ Description: %s\n", names[name]['description']) end
|
||||||
if(names[name]['flags'] ~= nil) then response = response .. string.format(" |_ Flags: %s\n", stdnse.strjoin(", ", names[name]['flags_list'])) end
|
if(names[name]['flags'] ~= nil) then response = response .. string.format(" |_ Flags: %s\n", stdnse.strjoin(", ", names[name]['flags'])) end
|
||||||
|
|
||||||
if(nmap.verbosity() > 1) then
|
if(nmap.verbosity() > 1) then
|
||||||
if(names[name]['source'] ~= nil) then response = response .. string.format(" |_ Source: %s\n", names[name]['source']) end
|
if(names[name]['source'] ~= nil) then response = response .. string.format(" |_ Source: %s\n", names[name]['source']) end
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
description = [[
|
description = [[
|
||||||
Attempts to determine the operating system over the SMB protocol (ports 445 and
|
Attempts to determine the operating system, computer name, domain, and current
|
||||||
139).
|
time over the SMB protocol (ports 445 or 139). This is done by starting a
|
||||||
|
session with the anonymous account (or with a proper user account, if one is
|
||||||
|
given); in response to a session starting, the server will send back all this
|
||||||
|
information.
|
||||||
|
|
||||||
|
Some systems, like Samba, will blank out their name (and only send their domain).
|
||||||
|
Other systems (like embedded printers) will simply leave out the information. Other
|
||||||
|
systems will blank out various pieces (some will send back '0' for the current
|
||||||
|
time, for example).
|
||||||
|
|
||||||
Although the standard <code>smb*</code> script arguments can be used,
|
Although the standard <code>smb*</code> script arguments can be used,
|
||||||
they likely won't change the outcome in any meaningful way.
|
they likely won't change the outcome in any meaningful way.
|
||||||
@@ -18,8 +26,9 @@ they likely won't change the outcome in any meaningful way.
|
|||||||
-- |_ System time: 2008-09-09 20:55:55 UTC-5
|
-- |_ System time: 2008-09-09 20:55:55 UTC-5
|
||||||
--
|
--
|
||||||
-- @args smb* This script supports the <code>smbusername</code>,
|
-- @args smb* This script supports the <code>smbusername</code>,
|
||||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
-- script arguments of the <code>smb</code> module, but they are unlikely
|
||||||
|
-- to be required.
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -92,7 +101,42 @@ action = function(host)
|
|||||||
-- Kill SMB
|
-- Kill SMB
|
||||||
smb.stop(state)
|
smb.stop(state)
|
||||||
|
|
||||||
|
if(state['os'] == nil and state['lanmanager'] == nil) then
|
||||||
|
if(nmap.debugging() > 0) then
|
||||||
|
return "Server didn't return OS details"
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if(state['os'] == nil) then
|
||||||
|
state['os'] = "Unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
if(state['lanmanager'] == nil) then
|
||||||
|
state['lanmanager'] = "Unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
if(state['domain'] == nil) then
|
||||||
|
state['domain'] = "Unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
if(state['server'] == nil) then
|
||||||
|
state['server'] = "Unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
if(state['date'] == nil) then
|
||||||
|
state['date'] = "Unknown"
|
||||||
|
end
|
||||||
|
|
||||||
|
if(state['timezone_str'] == nil) then
|
||||||
|
state['timezone_str'] = ""
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
return string.format("%s\nLAN Manager: %s\nName: %s\\%s\nSystem time: %s %s\n", get_windows_version(state['os']), state['lanmanager'], state['domain'], state['server'], state['date'], state['timezone_str'])
|
return string.format("%s\nLAN Manager: %s\nName: %s\\%s\nSystem time: %s %s\n", get_windows_version(state['os']), state['lanmanager'], state['domain'], state['server'], state['date'], state['timezone_str'])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,27 +3,23 @@ Returns information about the SMB security level determined by SMB.
|
|||||||
|
|
||||||
Here is how to interpret the output:
|
Here is how to interpret the output:
|
||||||
|
|
||||||
User-level authentication: Each user has a separate username/password that is used
|
* User-level authentication: Each user has a separate username/password that is used
|
||||||
to log into the system. This is the default setup of pretty much everything
|
to log into the system. This is the default setup of pretty much everything
|
||||||
these days.
|
these days.
|
||||||
|
* Share-level authentication: The anonymous account should be used to log in, then
|
||||||
Share-level authentication: The anonymous account should be used to log in, then
|
|
||||||
the password is given (in plaintext) when a share is accessed. All users who
|
the password is given (in plaintext) when a share is accessed. All users who
|
||||||
have access to the share use this password. This was the original way of doing
|
have access to the share use this password. This was the original way of doing
|
||||||
things, but isn't commonly seen, now. If a server uses share-level security,
|
things, but isn't commonly seen, now. If a server uses share-level security,
|
||||||
it is vulnerable to sniffing.
|
it is vulnerable to sniffing.
|
||||||
|
* Challenge/response passwords supported: If enabled, the server can accept any
|
||||||
Challenge/response passwords supported: If enabled, the server can accept any type of
|
type of password (plaintext, LM and NTLM, and LMv2 and NTLMv2). If it isn't set,
|
||||||
password:
|
the server can only accept plaintext passwords. Most servers are configured to
|
||||||
* Plaintext
|
use challenge/response these days. If a server is configured to accept plaintext
|
||||||
* LM and NTLM
|
passwords, it is vulnerable to sniffing. LM and NTLM are fairly secure, although
|
||||||
* LMv2 and NTLMv2
|
there are some brute-force attacks against them. Additionally, LM and NTLM can
|
||||||
If it isn't set, the server can only accept plaintext passwords. Most servers
|
fall victim to man-in-the-middle attacks or relay attacks (see MS08-068 or my
|
||||||
are configured to use challenge/response these days. If a server is configured
|
writeup of it: http://www.skullsecurity.org/blog/?p=110).
|
||||||
to accept plaintext passwords, it is vulnerable to sniffing. LM and NTLM are
|
* Message signing: If required, all messages between the client and server must
|
||||||
fairly secure, although there are some brute-force attacks against them.
|
|
||||||
|
|
||||||
Message signing: If required, all messages between the client and server must
|
|
||||||
be signed by a shared key, derived from the password and the server
|
be signed by a shared key, derived from the password and the server
|
||||||
challenge. If supported and not required, message signing is negotiated between
|
challenge. If supported and not required, message signing is negotiated between
|
||||||
clients and servers and used if both support and request it. By default,
|
clients and servers and used if both support and request it. By default,
|
||||||
@@ -31,7 +27,7 @@ Windows clients don't sign messages, so if message signing isn't required by
|
|||||||
the server, messages probably won't be signed; additionally, if performing a
|
the server, messages probably won't be signed; additionally, if performing a
|
||||||
man-in-the-middle attack, an attacker can negotiate no message signing. If
|
man-in-the-middle attack, an attacker can negotiate no message signing. If
|
||||||
message signing isn't required, the server is vulnerable to man-in-the-middle
|
message signing isn't required, the server is vulnerable to man-in-the-middle
|
||||||
attacks.
|
attacks or SMB-relay attacks.
|
||||||
|
|
||||||
This script will allow you to use the <code>smb*</code> script arguments (to
|
This script will allow you to use the <code>smb*</code> script arguments (to
|
||||||
set the username and password, etc.), but it probably won't ever require them.
|
set the username and password, etc.), but it probably won't ever require them.
|
||||||
@@ -48,8 +44,8 @@ set the username and password, etc.), but it probably won't ever require them.
|
|||||||
-- |_ smb-security-mode: Message signing supported
|
-- |_ smb-security-mode: Message signing supported
|
||||||
--
|
--
|
||||||
-- @args smb* This script supports the <code>smbusername</code>,
|
-- @args smb* This script supports the <code>smbusername</code>,
|
||||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
-- script arguments of the <code>smb</code> module.
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ of Windows, and Vista doesn't seem to let even the administrator account pull th
|
|||||||
|
|
||||||
Some of the numbers returned here don't feel right to me, but they're definitely
|
Some of the numbers returned here don't feel right to me, but they're definitely
|
||||||
the numbers that Windows returns. Take the values here with a grain of salt.
|
the numbers that Windows returns. Take the values here with a grain of salt.
|
||||||
|
|
||||||
|
These statistics are found using a single call to a SRVSVC function,
|
||||||
|
<code>NetServerGetStatistics</code>. This packet is parsed incorrectly by Wireshark,
|
||||||
|
up to version 1.0.3 (and possibly higher).
|
||||||
]]
|
]]
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -25,8 +29,8 @@ the numbers that Windows returns. Take the values here with a grain of salt.
|
|||||||
-- |_ |_ Files opened (including pipes): 18
|
-- |_ |_ Files opened (including pipes): 18
|
||||||
--
|
--
|
||||||
-- @args smb* This script supports the <code>smbusername</code>,
|
-- @args smb* This script supports the <code>smbusername</code>,
|
||||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
-- script arguments of the <code>smb</code> module.
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
author = "Ron Bowes"
|
author = "Ron Bowes"
|
||||||
@@ -80,10 +84,15 @@ action = function(host)
|
|||||||
smb.stop(smbstate)
|
smb.stop(smbstate)
|
||||||
|
|
||||||
-- Build the response
|
-- Build the response
|
||||||
|
local stats = netservergetstatistics_result['stat']
|
||||||
local response = " \n"
|
local response = " \n"
|
||||||
local period = os.time() - netservergetstatistics_result['start']
|
local period = os.time() - stats['start']
|
||||||
local period_str
|
local period_str
|
||||||
|
|
||||||
|
-- Fix a couple values
|
||||||
|
stats['bytessent'] = bit.bor(bit.lshift(stats['bytessent_high'], 32), stats['bytessent_low'])
|
||||||
|
stats['bytesrcvd'] = bit.bor(bit.lshift(stats['bytesrcvd_high'], 32), stats['bytesrcvd_low'])
|
||||||
|
|
||||||
if(period == 0) then
|
if(period == 0) then
|
||||||
period = 1
|
period = 1
|
||||||
end
|
end
|
||||||
@@ -96,8 +105,7 @@ action = function(host)
|
|||||||
period_str = string.format("%02dm%02ds", period / 60, period % 60)
|
period_str = string.format("%02dm%02ds", period / 60, period % 60)
|
||||||
end
|
end
|
||||||
|
|
||||||
stats = netservergetstatistics_result
|
response = response .. string.format("Server statistics collected since %s (%s):\n", os.date("%Y-%m-%d %H:%M:%S", stats['start']), period_str)
|
||||||
response = response .. string.format("Server statistics collected since %s (%s):\n", netservergetstatistics_result['start_date'], period_str)
|
|
||||||
response = response .. string.format("|_ Traffic %d bytes (%.2f b/s) sent, %d bytes (%.2f b/s) received\n", stats['bytessent'], stats['bytessent'] / period, stats['bytesrcvd'], stats['bytesrcvd'] / period)
|
response = response .. string.format("|_ Traffic %d bytes (%.2f b/s) sent, %d bytes (%.2f b/s) received\n", stats['bytessent'], stats['bytessent'] / period, stats['bytesrcvd'], stats['bytesrcvd'] / period)
|
||||||
response = response .. string.format("|_ Failed logins: %d\n", stats['pwerrors'])
|
response = response .. string.format("|_ Failed logins: %d\n", stats['pwerrors'])
|
||||||
response = response .. string.format("|_ Permission errors: %d, System errors: %d\n", stats['permerrors'], stats['syserrors'])
|
response = response .. string.format("|_ Permission errors: %d, System errors: %d\n", stats['permerrors'], stats['syserrors'])
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ This goes for all operating systems, including Windows 2000.
|
|||||||
|
|
||||||
Windows Vista doesn't appear to have the WINREG binding (or it's different and
|
Windows Vista doesn't appear to have the WINREG binding (or it's different and
|
||||||
I don't know it), so this doesn't support Vista at all.
|
I don't know it), so this doesn't support Vista at all.
|
||||||
|
|
||||||
|
If you know of more information stored in the Windows registry that could be interesting,
|
||||||
|
post a message to the nmap-dev mailing list and I (Ron Bowes) will add it to my todo list.
|
||||||
|
Adding new checks to this is extremely easy.
|
||||||
|
|
||||||
|
WARNING: I have experienced crashes in regsvc.exe while making registry calls against a fully patched Windows
|
||||||
|
2000 system; I've fixed the issue that caused it, but there's no guarantee that it (or a similar vuln in the
|
||||||
|
same code) won't show up again.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -20,7 +28,7 @@ I don't know it), so this doesn't support Vista at all.
|
|||||||
-- | OS Details
|
-- | OS Details
|
||||||
-- | |_ Microsoft Windows Server 2003 Service Pack 2 (ServerNT 5.2 build 3790)
|
-- | |_ Microsoft Windows Server 2003 Service Pack 2 (ServerNT 5.2 build 3790)
|
||||||
-- | |_ Installed on 2007-11-26 23:40:40
|
-- | |_ Installed on 2007-11-26 23:40:40
|
||||||
-- | |_ Registered to IPC (organization: MYCOMPANY)
|
-- | |_ Registered to Ron Bowes (organization: MYCOMPANY)
|
||||||
-- | |_ 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\Microsoft SQL Server\90\Tools\binn\;C:\Program Files\IBM\Rational AppScan\
|
||||||
-- | |_ Systemroot: C:\WINDOWS
|
-- | |_ Systemroot: C:\WINDOWS
|
||||||
-- | |_ Page files: C:\pagefile.sys 2046 4092 (cleared at shutdown => 0)
|
-- | |_ Page files: C:\pagefile.sys 2046 4092 (cleared at shutdown => 0)
|
||||||
@@ -39,8 +47,8 @@ I don't know it), so this doesn't support Vista at all.
|
|||||||
-- |_ |_ Firefox 3.0.3 (en-US)
|
-- |_ |_ Firefox 3.0.3 (en-US)
|
||||||
--
|
--
|
||||||
-- @args smb* This script supports the <code>smbusername</code>,
|
-- @args smb* This script supports the <code>smbusername</code>,
|
||||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
-- script arguments of the <code>smb</code> module.
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user