1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 04:31:29 +00:00

Merged in significant changes to Microsoft RPC calls

This commit is contained in:
ron
2008-12-07 16:16:11 +00:00
parent e6505d9954
commit ea42f39faa
15 changed files with 5107 additions and 1715 deletions

View File

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

File diff suppressed because it is too large Load Diff

3921
nselib/msrpctypes.lua Normal file

File diff suppressed because it is too large Load Diff

62
nselib/nmapdebug.lua Normal file
View 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

View File

@@ -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,21 +1290,19 @@ 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 = {}
data['username'] = 'guest' data['username'] = 'guest'
data['domain'] = '' data['domain'] = ''
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,

View File

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

View File

@@ -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.
@@ -66,70 +92,146 @@ local UNKNOWN = 3
-- <code>VULNERABLE</code> for vulnerable, <code>PATCHED</code> for not vulnerable, or -- <code>VULNERABLE</code> for vulnerable, <code>PATCHED</code> for not vulnerable, or
-- <code>UNKNOWN</code> if there was an error (likely vulnerable). -- <code>UNKNOWN</code> if there was an error (likely vulnerable).
function check_ms08_067(host) function check_ms08_067(host)
local status, smbstate local status, smbstate
local bind_result, netpathcompare_result local bind_result, netpathcompare_result
-- Create the SMB session -- Create the SMB session
status, smbstate = msrpc.start_smb(host, "\\\\BROWSER") status, smbstate = msrpc.start_smb(host, "\\\\BROWSER")
if(status == false) then if(status == false) then
return false, smbstate return false, smbstate
end end
-- Bind to SRVSVC service -- Bind to SRVSVC service
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil) status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then if(status == false) then
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
return false, bind_result return false, bind_result
end end
-- Call netpathcanonicalize -- Call netpathcanonicalize
-- status, netpathcanonicalize_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\a", "\\test\\") -- status, netpathcanonicalize_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\a", "\\test\\")
local path1 = "\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\..\\n" local path1 = "\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\..\\n"
local path2 = "\\n" local path2 = "\\n"
status, netpathcompare_result = msrpc.srvsvc_netpathcompare(smbstate, host.ip, path1, path2, 1, 0) status, netpathcompare_result = msrpc.srvsvc_netpathcompare(smbstate, host.ip, path1, path2, 1, 0)
-- Stop the SMB session -- Stop the SMB session
msrpc.stop_smb(smbstate) msrpc.stop_smb(smbstate)
if(status == false) then if(status == false) then
if(string.find(netpathcompare_result, "INVALID_NAME") == nil) then if(string.find(netpathcompare_result, "INVALID_NAME") == nil) then
return true, UNKNOWN return true, UNKNOWN
else else
return true, PATCHED return true, PATCHED
end end
end end
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

View File

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

View File

@@ -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,44 +196,42 @@ 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 if(status == false) then
stdnse.print_debug(3, "MSRPC: Looking up RID %s in SID %s", rid, msrpc.sid_to_string(elements[i]['sid'])) -- It may not succeed, if it doesn't that's ok
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], elements[i]['sid'], {rid}) stdnse.print_debug(3, "MSRPC: Lookup failed")
if(status == false) then
-- It may not succeed, if it doesn't that's ok
stdnse.print_debug(3, "MSRPC: Lookup failed")
else
-- Create the result array
local result = {}
result['rid'] = rid
result['changed_date'] = elements[i]['changed_date']
-- Fill in the result from the response
if(lookupsids2_result['details'][1] == nil) then
result['name'] = "<unknown>"
result['domain'] = ""
else else
result['name'] = lookupsids2_result['details'][1]['name'] -- Create the result array
result['type'] = lookupsids2_result['details'][1]['type'] local result = {}
result['domain'] = lookupsids2_result['domains'][1]['name'] result['changed_date'] = elements[i]['changed_date']
end result['rid'] = rid
if(result['type'] ~= 5) then -- Don't show "well known" accounts -- Fill in the result from the response
-- Add it to the results if(lookupsids2_result['names']['names'][1] == nil) then
results[#results + 1] = result result['name'] = "<unknown>"
result['type'] = "<unknown>"
result['domain'] = ""
else
result['name'] = lookupsids2_result['names']['names'][1]['name']
result['type'] = lookupsids2_result['names']['names'][1]['sid_type']
result['domain'] = lookupsids2_result['domains']['domains'][1]['name']
end
if(result['type'] ~= "SID_NAME_WKN_GRP") then -- Don't show "well known" accounts
-- Add it to the results
results[#results + 1] = result
end
end end
end end
end end
@@ -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

View File

@@ -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'])

View File

@@ -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
@@ -211,25 +241,53 @@ 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']
-- Call QueryDisplayInfo() -- Loop as long as we're getting valid results
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle) j = 0
if(status == false) then repeat
msrpc.stop_smb(smbstate) -- Call QueryDisplayInfo()
return false, querydisplayinfo_result status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle, j)
end if(status == false) then
msrpc.stop_smb(smbstate)
return false, querydisplayinfo_result
end
-- Save the response
if(querydisplayinfo_result['return'] ~= 0 and querydisplayinfo_result['info'] ~= nil and querydisplayinfo_result['info']['entries'] ~= nil and querydisplayinfo_result['info']['entries'][1] ~= nil) then
local array = {}
local k
-- The reason these are all indexed from '1' is because we request names one at a time.
array['domain'] = domain
array['name'] = querydisplayinfo_result['info']['entries'][1]['account_name']
array['fullname'] = querydisplayinfo_result['info']['entries'][1]['full_name']
array['description'] = querydisplayinfo_result['info']['entries'][1]['description']
array['rid'] = querydisplayinfo_result['info']['entries'][1]['rid']
array['flags'] = querydisplayinfo_result['info']['entries'][1]['acct_flags']
array['source'] = "SAMR Enumeration"
-- Clean up the 'flags' array
for k = 1, #array['flags'], 1 do
array['flags'][k] = msrpc.samr_AcctFlags_tostr(array['flags'][k])
end
-- Add it to the array
response[#response + 1] = array
end
j = j + 1
until querydisplayinfo_result['return'] == 0
-- Close the domain handle -- 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,38 +435,50 @@ 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.
lsa_status, lsa_result = enum_lsa(host) if(do_lsa) then
if(lsa_status == false) then lsa_status, lsa_result = enum_lsa(host)
if(nmap.debugging() > 0) then if(lsa_status == false) then
response = response .. "ERROR: couldn't enum through LSA: " .. lsa_result .. "\n" if(nmap.debugging() > 0) then
end response = response .. "ERROR: Couldn't enumerate through LSA: " .. lsa_result .. "\n"
else end
-- Copy the returned array into the names[] table, using the name as the key else
stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result) -- Copy the returned array into the names[] table, using the name as the key
for i = 1, #lsa_result, 1 do stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
names[string.upper(lsa_result[i]['name'])] = lsa_result[i] for i = 1, #lsa_result, 1 do
if(lsa_result[i]['name'] ~= nil) then
names[string.upper(lsa_result[i]['name'])] = lsa_result[i]
end
end
end end
end end
-- Try enumerating through SAMR -- Try enumerating through SAMR
samr_status, samr_result = enum_samr(host) if(do_samr) then
if(samr_status == false) then samr_status, samr_result = enum_samr(host)
if(nmap.debugging() > 0) then if(samr_status == false) then
response = response .. "ERROR: couldn't enumerate through SAMR: " .. samr_result .. "\n" if(nmap.debugging() > 0) then
end response = response .. "ERROR: Couldn't enumerate through SAMR: " .. samr_result .. "\n"
else end
-- Copy the returned array into the names[] table, using the name as the key else
stdnse.print_debug(2, "EnumUsers: Received %d names from SAMR", #samr_result) -- Copy the returned array into the names[] table, using the name as the key
for i = 1, #samr_result, 1 do stdnse.print_debug(2, "EnumUsers: Received %d names from SAMR", #samr_result)
names[string.upper(samr_result[i]['name'])] = samr_result[i] for i = 1, #samr_result, 1 do
names[string.upper(samr_result[i]['name'])] = samr_result[i]
end
end end
end end
@@ -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

View File

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

View File

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

View File

@@ -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"
@@ -79,11 +83,16 @@ action = function(host)
-- Stop the session -- Stop the session
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,9 +105,8 @@ 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 (%.2fb/s) sent, %d bytes (%.2fb/s) received\n", stats['bytessent'], stats['bytessent'] / period, stats['bytesrcvd'], stats['bytesrcvd'] / period)
response = response .. string.format("|_ Failed logins: %d\n", stats['pwerrors']) response = response .. string.format("|_ 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'])
response = response .. string.format("|_ Print jobs spooled: %s\n", stats['jobsqueued']) response = response .. string.format("|_ Print jobs spooled: %s\n", stats['jobsqueued'])

View File

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