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:
@@ -1,5 +1,12 @@
|
||||
# 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
|
||||
completely replaces the old diff view. ndiff is now required to do
|
||||
comparisons in Zenmap. [David]
|
||||
|
||||
1907
nselib/msrpc.lua
1907
nselib/msrpc.lua
File diff suppressed because it is too large
Load Diff
3921
nselib/msrpctypes.lua
Normal file
3921
nselib/msrpctypes.lua
Normal file
File diff suppressed because it is too large
Load Diff
62
nselib/nmapdebug.lua
Normal file
62
nselib/nmapdebug.lua
Normal file
@@ -0,0 +1,62 @@
|
||||
--- Debugging functions for Nmap scripts.
|
||||
--
|
||||
-- This module contains various handy functions for debugging. These should
|
||||
-- never be used for actual results, only during testing.
|
||||
--
|
||||
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
||||
|
||||
local require = require
|
||||
local type = type
|
||||
local pairs = pairs
|
||||
local nmap = require "nmap";
|
||||
local stdnse = require "stdnse";
|
||||
|
||||
local EMPTY = {}; -- Empty constant table
|
||||
|
||||
module(... or "nmapdebug");
|
||||
|
||||
---Converts an arbitrary data type into a string. Will recursively convert
|
||||
-- tables. This can be very useful for debugging.
|
||||
--
|
||||
--@param data The data to convert.
|
||||
--@param indent (optional) The number of times to indent the line. Default
|
||||
-- is 0.
|
||||
--@return A string representation of a data, will be one or more full lines.
|
||||
function tostr(data, indent)
|
||||
local str = ""
|
||||
|
||||
if(indent == nil) then
|
||||
indent = 0
|
||||
end
|
||||
|
||||
-- Check the type
|
||||
if(type(data) == "nil") then
|
||||
str = str .. (" "):rep(indent) .. data .. "\n"
|
||||
elseif(type(data) == "string") then
|
||||
str = str .. (" "):rep(indent) .. data .. "\n"
|
||||
elseif(type(data) == "number") then
|
||||
str = str .. (" "):rep(indent) .. data .. "\n"
|
||||
elseif(type(data) == "boolean") then
|
||||
if(data == true) then
|
||||
str = str .. "true"
|
||||
else
|
||||
str = str .. "false"
|
||||
end
|
||||
elseif(type(data) == "table") then
|
||||
local i, v
|
||||
for i, v in pairs(data) do
|
||||
-- Check for a table in a table
|
||||
if(type(v) == "table") then
|
||||
str = str .. (" "):rep(indent) .. i .. ":\n"
|
||||
str = str .. tostr(v, indent + 2)
|
||||
else
|
||||
str = str .. (" "):rep(indent) .. i .. ": " .. tostr(v, 0)
|
||||
end
|
||||
end
|
||||
else
|
||||
stdnse.print_debug(1, "Error: unknown data type: %s", type(data))
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
118
nselib/smb.lua
118
nselib/smb.lua
@@ -1,10 +1,18 @@
|
||||
--- Server Message Block (SMB, also known as CIFS) traffic.
|
||||
--
|
||||
-- SMB traffic is normally
|
||||
-- sent to/from ports 139 or 445 of Windows systems, although it's also implemented by
|
||||
-- other systems (the most notable one being Samba).
|
||||
-- SMB traffic is normally sent to/from ports 139 or 445 of Windows systems, some of them
|
||||
-- properly and many of them not. Samba implements it, as do many printers and other embedded
|
||||
-- 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,
|
||||
-- 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:
|
||||
@@ -31,13 +39,14 @@
|
||||
-- status, err = smb.negotiate_protocol(smbstate)
|
||||
-- status, err = smb.start_session(smbstate)
|
||||
-- status, err = smb.tree_connect(smbstate, path)
|
||||
-- ...
|
||||
-- status, err = smb.tree_disconnect(smbstate)
|
||||
-- status, err = smb.logoff(smbstate)
|
||||
-- status, err = smb.stop(smbstate)
|
||||
--</code>
|
||||
--
|
||||
-- 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:
|
||||
--
|
||||
@@ -48,11 +57,11 @@
|
||||
-- That packet requires the computer's name, which is requested
|
||||
-- using a NBSTAT probe over UDP port 137.
|
||||
--
|
||||
-- Once it's connected, a <code>SMB_COM_NEGOTIATE</code> packet is sent,
|
||||
-- requesting the protocol "NT LM 0.12", which is the most commonly
|
||||
-- supported one. Among other things, the server's response contains
|
||||
-- the host's security level, the system time, and the computer/domain
|
||||
-- name.
|
||||
-- Once it's connected, a <code>SMB_COM_NEGOTIATE</code> packet is sent, requesting the protocol
|
||||
-- "NT LM 0.12", which is the most commonly supported one. Among other things, the server's
|
||||
-- response contains the host's security level, the system time, and the computer/domain name.
|
||||
-- Some systems will refuse to use that protocol and return "-1" or "1" instead of 0. If that's
|
||||
-- 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
|
||||
-- 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).
|
||||
--
|
||||
-- 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
|
||||
-- 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).
|
||||
-- Lanman is horribly insecure, though, so I don't send it at all. These options can, however,
|
||||
-- be overridden either through script parameters or registry settings [TODO].
|
||||
-- isn't sent at all. The reason for this is, NTLMv2 isn't supported by every system (and I
|
||||
-- don't know how to do message signing on the v2 protocols), and doesn't have a significant security
|
||||
-- advantage over NTLMv1 for performing single scans (the major change in NTLMv2 is incorporating
|
||||
-- a client challenge). Lanman is somewhat insecure, though, so I don't send it at all. These options
|
||||
-- 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
|
||||
-- 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-
|
||||
@@ -82,24 +91,8 @@
|
||||
--
|
||||
-- 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
|
||||
-- pwdump and fgdump, or read straight from the SAM file. 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.
|
||||
-- pwdump and fgdump, or read straight from the SAM file (maybe some day, I'll do an Nmap script to dump it).
|
||||
-- This means that if a password file is recovered, it doesn't even need to be cracked before it can be used here.
|
||||
--
|
||||
-- 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
|
||||
@@ -109,7 +102,6 @@
|
||||
--
|
||||
-- Scripts that use this module can use the script arguments
|
||||
-- <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:
|
||||
-- <code>
|
||||
-- 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,
|
||||
-- and are stored on disk or in memory. They can be retrieved from memory
|
||||
-- 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:
|
||||
-- * <code>v1</code>: Sends LMv1 and NTLMv1.
|
||||
-- * <code>LMv1</code>: Sends LMv1 only.
|
||||
@@ -160,6 +149,7 @@ status_codes = {}
|
||||
status_names = {}
|
||||
|
||||
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
|
||||
-- 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
|
||||
|
||||
-- if(nmap.debugging() > 0) then
|
||||
-- return debug_mutex
|
||||
-- end
|
||||
|
||||
-- Decide whether to use the name or the ip address as the unique identifier
|
||||
if(smbstate['name'] ~= nil) then
|
||||
mutex_name = mutex_name .. smbstate['name']
|
||||
@@ -842,11 +836,13 @@ function smb_read(smb)
|
||||
end
|
||||
|
||||
-- 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)
|
||||
if(netbios_length == nil) then
|
||||
return false, "SMB: Malformed packet received"
|
||||
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)
|
||||
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)
|
||||
else
|
||||
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
|
||||
|
||||
@@ -1185,7 +1181,7 @@ local function get_password_response(username, domain, password, password_hash,
|
||||
lm_hash = bin.pack("H", hashes:sub(1, 32))
|
||||
ntlm_hash = bin.pack("H", hashes:sub(34, 65))
|
||||
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
|
||||
ntlm_hash = nil
|
||||
end
|
||||
@@ -1294,21 +1290,19 @@ local function get_logins(ip, challenge, username, domain, password, password_ha
|
||||
|
||||
end
|
||||
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
|
||||
|
||||
local data
|
||||
-- Add guest account, if they requested
|
||||
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")
|
||||
-- Add guest account
|
||||
stdnse.print_debug(2, "SMB: Going to try guest account before attempting anonymous")
|
||||
|
||||
data = {}
|
||||
data['username'] = 'guest'
|
||||
data['domain'] = ''
|
||||
data['lanman'] = ''
|
||||
data['ntlm'] = ''
|
||||
response[#response + 1] = data
|
||||
end
|
||||
data = {}
|
||||
data['username'] = 'guest'
|
||||
data['domain'] = ''
|
||||
data['lanman'] = ''
|
||||
data['ntlm'] = ''
|
||||
response[#response + 1] = data
|
||||
|
||||
-- Add the anonymous account
|
||||
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]
|
||||
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
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
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)
|
||||
|
||||
end
|
||||
@@ -1751,14 +1745,17 @@ function send_transaction(smb, func, function_parameters, function_data)
|
||||
return false, "SMB: Malformed packet received"
|
||||
end
|
||||
if(status ~= 0) then
|
||||
io.write(string.format("Status = %08x\n\n", status))
|
||||
return false, status_codes[status]
|
||||
if(status_names[status] == nil) then
|
||||
return false, string.format("Unknown SMB error: 0x%08x\n", status)
|
||||
else
|
||||
return false, status_names[status]
|
||||
end
|
||||
end
|
||||
|
||||
-- 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)
|
||||
if(reserved2 == nil) then
|
||||
return "SMB: Malformed packet received"
|
||||
return false, "SMB: Malformed packet received"
|
||||
end
|
||||
|
||||
-- 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_WERR_BADFILE = 0x00000002,
|
||||
NT_STATUS_WERR_ACCESS_DENIED = 0x00000005,
|
||||
NT_STATUS_WERR_INVALID_NAME = 0x0000007b,
|
||||
NT_STATUS_WERR_UNKNOWN_LEVEL = 0x0000007c,
|
||||
NT_STATUS_NO_MORE_ITEMS = 0x00000103,
|
||||
NT_STATUS_MORE_ENTRIES = 0x00000105,
|
||||
NT_STATUS_SOME_NOT_MAPPED = 0x00000107,
|
||||
DOS_STATUS_UNKNOWN_ERROR = 0x00010001,
|
||||
DOS_STATUS_ACCESS_DENIED = 0x00050001,
|
||||
NT_STATUS_BUFFER_OVERFLOW = 0x80000005,
|
||||
NT_STATUS_UNSUCCESSFUL = 0xc0000001,
|
||||
NT_STATUS_NOT_IMPLEMENTED = 0xc0000002,
|
||||
|
||||
@@ -12,6 +12,7 @@ local nmap = require"nmap";
|
||||
local max = math.max
|
||||
local ceil = math.ceil
|
||||
local type = type
|
||||
local pairs = pairs
|
||||
|
||||
local EMPTY = {}; -- Empty constant table
|
||||
|
||||
|
||||
@@ -4,16 +4,36 @@ Checks if a host is vulnerable to MS08-067, a Windows RPC vulnerability that
|
||||
can allow remote code execution. This script is intended to check for more
|
||||
vulnerabilities in the future.
|
||||
|
||||
Checking for MS08-067 is very dangerous, as the check is likely
|
||||
to crash systems. On a fairly wide scan conducted by Brandon Enright, we determined
|
||||
WARNING: These checks are dangerous, and are very likely to bring down a server.
|
||||
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
|
||||
the check. Out of 82 vulnerable systems, 52 crashed. As such, great care should be
|
||||
taken when using this check.
|
||||
the check. Out of 82 vulnerable systems, 52 crashed.
|
||||
|
||||
You have the option to supply a username and password, but
|
||||
it shouldn't be necessary for a default configuration.
|
||||
At the same time, MS08-067 is extremely critical to fix. Metasploit has a working and
|
||||
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
|
||||
-- checks for more vulnerabilities, but I'm worried about licensing/copyright issues
|
||||
-- (since I'd be basing them on non-free tools).
|
||||
@@ -25,11 +45,16 @@ it shouldn't be necessary for a default configuration.
|
||||
--
|
||||
--@output
|
||||
-- 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>,
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||
-- 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"
|
||||
@@ -48,6 +73,7 @@ end
|
||||
local VULNERABLE = 1
|
||||
local PATCHED = 2
|
||||
local UNKNOWN = 3
|
||||
local NOTRUN = 4
|
||||
|
||||
---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
|
||||
@@ -58,7 +84,7 @@ local UNKNOWN = 3
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
--@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>UNKNOWN</code> if there was an error (likely vulnerable).
|
||||
function check_ms08_067(host)
|
||||
local status, smbstate
|
||||
local bind_result, netpathcompare_result
|
||||
local status, smbstate
|
||||
local bind_result, netpathcompare_result
|
||||
|
||||
-- Create the SMB session
|
||||
status, smbstate = msrpc.start_smb(host, "\\\\BROWSER")
|
||||
if(status == false) then
|
||||
return false, smbstate
|
||||
end
|
||||
-- Create the SMB session
|
||||
status, smbstate = msrpc.start_smb(host, "\\\\BROWSER")
|
||||
if(status == false) then
|
||||
return false, smbstate
|
||||
end
|
||||
|
||||
-- Bind to SRVSVC service
|
||||
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, bind_result
|
||||
end
|
||||
-- Bind to SRVSVC service
|
||||
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, bind_result
|
||||
end
|
||||
|
||||
-- Call netpathcanonicalize
|
||||
-- status, netpathcanonicalize_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\a", "\\test\\")
|
||||
-- Call netpathcanonicalize
|
||||
-- status, netpathcanonicalize_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\a", "\\test\\")
|
||||
|
||||
local path1 = "\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\..\\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
|
||||
msrpc.stop_smb(smbstate)
|
||||
-- Stop the SMB session
|
||||
msrpc.stop_smb(smbstate)
|
||||
|
||||
if(status == false) then
|
||||
if(status == false) then
|
||||
if(string.find(netpathcompare_result, "INVALID_NAME") == nil) then
|
||||
return true, UNKNOWN
|
||||
else
|
||||
return true, PATCHED
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return true, VULNERABLE
|
||||
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)
|
||||
local status, result
|
||||
local response = " \n"
|
||||
local found = false
|
||||
|
||||
-- Check for ms08-067
|
||||
status, result = check_ms08_067(host)
|
||||
|
||||
if(status == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. result
|
||||
return "MS08-067: ERROR: " .. result
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
if(result == VULNERABLE) then
|
||||
response = "This host is vulnerable to MS08-067"
|
||||
response = response .. "MS08-067: VULNERABLE\n"
|
||||
found = true
|
||||
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
|
||||
if(nmap.verbosity() > 0) then
|
||||
response = "This host is patched for MS08-067"
|
||||
else
|
||||
response = nil
|
||||
response = response .. "MS08-067: FIXED\n"
|
||||
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
|
||||
end
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ After the initial <code>bind</code> to SAMR, the sequence of calls is:
|
||||
-- |_ |_ Administrator account cannot be locked out
|
||||
--
|
||||
-- @args smb* This script supports the <code>smbusername</code>,
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||
-- script arguments of the <code>smb</code> module.
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
author = "Ron Bowes"
|
||||
@@ -59,6 +59,7 @@ end
|
||||
action = function(host)
|
||||
local response = " \n"
|
||||
local status, smbstate
|
||||
local i, j
|
||||
|
||||
-- Create the SMB session
|
||||
status, smbstate = msrpc.start_smb(host, msrpc.SAMR_PATH)
|
||||
@@ -107,7 +108,7 @@ action = function(host)
|
||||
end
|
||||
|
||||
-- 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
|
||||
return "ERROR: Couldn't find any domains to check"
|
||||
else
|
||||
@@ -115,9 +116,9 @@ action = function(host)
|
||||
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 domain_handle
|
||||
|
||||
@@ -131,7 +132,6 @@ action = function(host)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Save the sid
|
||||
sid = lookupdomain_result['sid']
|
||||
|
||||
@@ -149,33 +149,32 @@ action = function(host)
|
||||
-- Save the domain handle
|
||||
domain_handle = opendomain_result['domain_handle']
|
||||
|
||||
-- Call QueryDomainInfo2() to get domain properties. We call these for three types == 1, 8, and 12, since those return
|
||||
-- Call QueryDomainInfo2() to get domain properties. We call these for three types -- 1, 8, and 12, since those return
|
||||
-- the most useful information.
|
||||
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 1)
|
||||
if(status == false) then
|
||||
status_1, querydomaininfo2_result_1 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 1)
|
||||
status_8, querydomaininfo2_result_8 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 8)
|
||||
status_12, querydomaininfo2_result_12 = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 12)
|
||||
|
||||
if(status_1 == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. querydomaininfo2_result
|
||||
return "ERROR: " .. querydomaininfo2_result_1
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 8, querydomaininfo2_result)
|
||||
if(status == false) then
|
||||
if(status_8 == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. querydomaininfo2_result
|
||||
return "ERROR: " .. querydomaininfo2_result_8
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
status, querydomaininfo2_result = msrpc.samr_querydomaininfo2(smbstate, domain_handle, 12, querydomaininfo2_result)
|
||||
if(status == false) then
|
||||
if(status_12 == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. querydomaininfo2_result
|
||||
return "ERROR: " .. querydomaininfo2_result_12
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@@ -185,7 +184,7 @@ action = function(host)
|
||||
status, enumdomainusers_result = msrpc.samr_enumdomainusers(smbstate, domain_handle)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
if(nmap.debugging() > 0) then
|
||||
if(nmap.debugging() > 0 and enumdomainusers_result ~= nil) then
|
||||
return "ERROR: " .. enumdomainusers_result
|
||||
else
|
||||
return nil
|
||||
@@ -195,16 +194,30 @@ action = function(host)
|
||||
-- Close the domain handle
|
||||
msrpc.samr_close(smbstate, domain_handle)
|
||||
|
||||
-- Create the list of users
|
||||
local names = {}
|
||||
if(enumdomainusers_result['sam'] ~= nil and enumdomainusers_result['sam']['entries'] ~= nil) then
|
||||
for j = 1, #enumdomainusers_result['sam']['entries'], 1 do
|
||||
local name = enumdomainusers_result['sam']['entries'][j]['name']
|
||||
names[#names + 1] = name
|
||||
end
|
||||
end
|
||||
|
||||
-- Finally, fill in the response!
|
||||
response = response .. string.format("Domain: %s\n", domain)
|
||||
response = response .. string.format(" |_ SID: %s\n", msrpc.sid_to_string(lookupdomain_result['sid']))
|
||||
response = response .. string.format(" |_ Users: %s\n", stdnse.strjoin(", ", enumdomainusers_result['names']))
|
||||
response = response .. string.format(" |_ Creation time: %s\n", querydomaininfo2_result['create_date'])
|
||||
response = response .. string.format(" |_ SID: %s\n", lookupdomain_result['sid'])
|
||||
if(#names ~= 0) then
|
||||
response = response .. string.format(" |_ Users: %s\n", stdnse.strjoin(", ", names))
|
||||
end
|
||||
|
||||
if(querydomaininfo2_result_8['info']['domain_create_time'] ~= 0) then
|
||||
response = response .. string.format(" |_ Creation time: %s\n", os.date("%Y-%m-%d %H:%M:%S", querydomaininfo2_result_8['info']['domain_create_time']))
|
||||
end
|
||||
|
||||
-- Password characteristics
|
||||
local min_password_length = querydomaininfo2_result['min_password_length']
|
||||
local max_password_age = querydomaininfo2_result['max_password_age']
|
||||
local min_password_age = querydomaininfo2_result['min_password_age']
|
||||
local min_password_length = querydomaininfo2_result_1['info']['min_password_length']
|
||||
local max_password_age = querydomaininfo2_result_1['info']['max_password_age'] / 60 / 60 / 24
|
||||
local min_password_age = querydomaininfo2_result_1['info']['min_password_age'] / 60 / 60 / 24
|
||||
|
||||
if(min_password_length > 0) then
|
||||
min_password_length = string.format("%d characters", min_password_length)
|
||||
@@ -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)
|
||||
|
||||
local lockout_duration = querydomaininfo2_result['lockout_duration']
|
||||
local lockout_duration = querydomaininfo2_result_12['info']['lockout_duration']
|
||||
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
|
||||
lockout_duration = "until manually reset"
|
||||
end
|
||||
|
||||
if(querydomaininfo2_result['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)
|
||||
if(querydomaininfo2_result_12['info']['lockout_threshold'] > 0) then
|
||||
response = response .. string.format(" |_ Password lockout: %d attempts in under %d minutes will lock the account %s\n", querydomaininfo2_result_12['info']['lockout_threshold'], querydomaininfo2_result_12['info']['lockout_window'] / 60, lockout_duration)
|
||||
else
|
||||
response = response .. string.format(" |_ Account lockout disabled\n")
|
||||
end
|
||||
|
||||
if(querydomaininfo2_result['password_history_length']) > 0 then
|
||||
response = response .. string.format(" |_ Password history : %d passwords\n", querydomaininfo2_result['password_history_length'])
|
||||
if(querydomaininfo2_result_1['info']['password_history_length']) > 0 then
|
||||
response = response .. string.format(" |_ Password history: %d passwords\n", querydomaininfo2_result_1['info']['password_history_length'])
|
||||
end
|
||||
|
||||
if(#querydomaininfo2_result['password_properties_list'] > 0) then
|
||||
response = response .. " |_ Password properties: \n |_ " .. stdnse.strjoin("\n |_ ", querydomaininfo2_result['password_properties_list']) .. "\n"
|
||||
local password_properties = querydomaininfo2_result_1['info']['password_properties']
|
||||
if(#password_properties > 0) then
|
||||
response = response .. " |_ Password properties:\n"
|
||||
for j = 1, #password_properties, 1 do
|
||||
response = response .. " |_ " .. msrpc.samr_PasswordProperties_tostr(password_properties[j]) .. "\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
description = [[
|
||||
Enumerates the users logged into a system either locally, through a remote desktop client (terminal
|
||||
services), or through a SMB share.
|
||||
Enumerates the users logged into a system either locally or through an 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
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
idea to write this one.
|
||||
function calls to what they use, so thanks go out to them. Thanks also to Matt Gardenghi, for requesting
|
||||
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]
|
||||
--
|
||||
-- @args smb* This script supports the <code>smbusername</code>,
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||
-- script arguments of the <code>smb</code> module.
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
author = "Ron Bowes"
|
||||
@@ -89,12 +92,15 @@ local function srvsvc_enum_sessions(host)
|
||||
-- Stop the SMB session
|
||||
msrpc.stop_smb(smbstate)
|
||||
|
||||
return true, netsessenum_result['sessions']
|
||||
return true, netsessenum_result['ctr']['array']
|
||||
end
|
||||
|
||||
---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.
|
||||
--
|
||||
-- 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.
|
||||
--@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).
|
||||
@@ -124,14 +130,13 @@ local function winreg_enum_rids(host)
|
||||
-- Loop through the keys under HKEY_USERS and grab the names
|
||||
i = 0
|
||||
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
|
||||
local status, openkey_result
|
||||
|
||||
local element = {}
|
||||
element['name'] = enumkey_result['name']
|
||||
element['sid'] = msrpc.string_to_sid(enumkey_result['name'])
|
||||
element['name'] = enumkey_result['name']
|
||||
|
||||
-- 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
|
||||
@@ -191,44 +196,42 @@ local function winreg_enum_rids(host)
|
||||
return false, openpolicy2_result
|
||||
end
|
||||
|
||||
-- Convert the RIDs to names
|
||||
-- Convert the SID to the name of the user
|
||||
local results = {}
|
||||
stdnse.print_debug(3, "MSRPC: Found %d SIDs that might be logged in", #elements)
|
||||
for i = 1, #elements, 1 do
|
||||
if(elements[i]['sid'] ~= nil) then
|
||||
-- The RID is the last subauthority
|
||||
local rid = elements[i]['sid']['subauthorities'][elements[i]['sid']['count']]
|
||||
stdnse.print_debug(3, "MSRPC: Found an actual RID: %d", rid)
|
||||
if(elements[i]['name'] ~= nil) then
|
||||
local sid = elements[i]['name']
|
||||
if(string.find(sid, "^S-") ~= nil and string.find(sid, "-%d+$") ~= nil) then
|
||||
-- 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
|
||||
elements[i]['sid']['subauthorities'][elements[i]['sid']['count']] = nil
|
||||
elements[i]['sid']['count'] = elements[i]['sid']['count'] - 1
|
||||
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], {elements[i]['name']})
|
||||
|
||||
-- Look up the RID
|
||||
stdnse.print_debug(3, "MSRPC: Looking up RID %s in SID %s", rid, msrpc.sid_to_string(elements[i]['sid']))
|
||||
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], elements[i]['sid'], {rid})
|
||||
if(status == false) then
|
||||
-- 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'] = ""
|
||||
if(status == false) then
|
||||
-- It may not succeed, if it doesn't that's ok
|
||||
stdnse.print_debug(3, "MSRPC: Lookup failed")
|
||||
else
|
||||
result['name'] = lookupsids2_result['details'][1]['name']
|
||||
result['type'] = lookupsids2_result['details'][1]['type']
|
||||
result['domain'] = lookupsids2_result['domains'][1]['name']
|
||||
end
|
||||
-- Create the result array
|
||||
local result = {}
|
||||
result['changed_date'] = elements[i]['changed_date']
|
||||
result['rid'] = rid
|
||||
|
||||
if(result['type'] ~= 5) then -- Don't show "well known" accounts
|
||||
-- Add it to the results
|
||||
results[#results + 1] = result
|
||||
-- Fill in the result from the response
|
||||
if(lookupsids2_result['names']['names'][1] == nil) then
|
||||
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
|
||||
@@ -275,29 +278,29 @@ action = function(host)
|
||||
-- Format the result
|
||||
for i = 1, #sessions, 1 do
|
||||
|
||||
local active = sessions[i]['active']
|
||||
if(active == 0) then
|
||||
active = "[just logged in, it's probably you]"
|
||||
elseif(active > 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)
|
||||
elseif(active > 60 * 60) then
|
||||
active = string.format("%dh%02dm%02ds", active / 3600, (active % 3600) / 60, active % 60)
|
||||
local time = sessions[i]['time']
|
||||
if(time == 0) then
|
||||
time = "[just logged in, it's probably you]"
|
||||
elseif(time > 60 * 60 * 24) then
|
||||
time = string.format("%dd%dh%02dm%02ds", time / (60*60*24), (time % (60*60*24)) / 3600, (time % 3600) / 60, time % 60)
|
||||
elseif(time > 60 * 60) then
|
||||
time = string.format("%dh%02dm%02ds", time / 3600, (time % 3600) / 60, time % 60)
|
||||
else
|
||||
active = string.format("%02dm%02ds", active / 60, active % 60)
|
||||
time = string.format("%02dm%02ds", time / 60, time % 60)
|
||||
end
|
||||
|
||||
local idle = sessions[i]['idle']
|
||||
if(idle == 0) then
|
||||
idle = "[not idle]"
|
||||
elseif(idle > 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)
|
||||
elseif(idle > 60 * 60) then
|
||||
idle = string.format("%dh%02dm%02ds", idle / 3600, (idle % 3600) / 60, idle % 60)
|
||||
local idle_time = sessions[i]['idle_time']
|
||||
if(idle_time == 0) then
|
||||
idle_time = "[not idle]"
|
||||
elseif(idle_time > 60 * 60 * 24) then
|
||||
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_time > 60 * 60) then
|
||||
idle_time = string.format("%dh%02dm%02ds", idle_time / 3600, (idle_time % 3600) / 60, idle_time % 60)
|
||||
else
|
||||
idle = string.format("%02dm%02ds", idle / 60, idle % 60)
|
||||
idle_time = string.format("%02dm%02ds", idle_time / 60, idle_time % 60)
|
||||
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
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
description = [[
|
||||
Attempts to list shares using the <code>srvsvc.NetShareEnumAll</code> MSRPC function, then
|
||||
retrieve more information about each share using <code>srvsvc.NetShareGetInfo</code>.
|
||||
Attempts to list shares using the <code>srvsvc.NetShareEnumAll</code> MSRPC function and
|
||||
retrieve more information about them using <code>srvsvc.NetShareGetInfo</code>.
|
||||
|
||||
Running
|
||||
<code>NetShareEnumAll</code> will work anonymously on Windows 2000, and requires a user-level
|
||||
account on any other Windows version. Calling <code>NetShareGetInfo</code> requires an
|
||||
administrator account on every version of Windows I (Ron Bowes) tested.
|
||||
Running <code>NetShareEnumAll</code> will work anonymously against Windows 2000, and
|
||||
requires a user-level account on any other Windows version. Calling <code>NetShareGetInfo</code>
|
||||
requires an administrator account on every version of Windows I (Ron Bowes) tested.
|
||||
|
||||
Although <code>NetShareEnumAll</code> is restricted on certain systems, actually connecting to
|
||||
a share to check if it exists will always work. So, if <code>NetShareEnumAll</code> fails, a
|
||||
list of common shares will be attempted.
|
||||
Although <code>NetShareEnumAll</code> is restricted on certain systems, making a connection to
|
||||
a share to check whether or not it exists will always work. So, if <code>NetShareEnumAll</code>
|
||||
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
|
||||
to each of them anonymously, which lets us divide them into the classes
|
||||
"anonymous" and "restricted."
|
||||
After a list of shares is found, we attempt to connect to each of them anonymously, which lets
|
||||
us divide them into the classes "anonymous" and "restricted."
|
||||
|
||||
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
|
||||
doing an authenticated test.
|
||||
to get additional information on them. Odds are this will fail, unless we're doing an authenticated
|
||||
test.
|
||||
]]
|
||||
|
||||
---
|
||||
@@ -58,8 +56,8 @@ doing an authenticated test.
|
||||
-- |_ |_ Path: C:\
|
||||
--
|
||||
-- @args smb* This script supports the <code>smbusername</code>,
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||
-- script arguments of the <code>smb</code> module.
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
author = "Ron Bowes"
|
||||
@@ -85,6 +83,8 @@ local function samr_enum_shares(host)
|
||||
|
||||
local status, smbstate
|
||||
local bind_result, netshareenumall_result
|
||||
local shares
|
||||
local i, v
|
||||
|
||||
-- Create the SMB session
|
||||
status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
|
||||
@@ -109,7 +109,13 @@ local function samr_enum_shares(host)
|
||||
-- Stop the SMB session
|
||||
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
|
||||
|
||||
---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")
|
||||
denied_shares[#denied_shares + 1] = shares[i]
|
||||
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
|
||||
else
|
||||
-- Add it to allowed shares
|
||||
@@ -227,11 +233,12 @@ action = function(host)
|
||||
-- best we can do.
|
||||
if(result == false) 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
|
||||
|
||||
-- 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 '$'
|
||||
for i = string.byte("A", 1), string.byte("Z", 1), 1 do
|
||||
shares[#shares + 1] = string.char(i)
|
||||
@@ -260,13 +267,15 @@ action = function(host)
|
||||
response = response .. string.format(" %s\n", allowed[i])
|
||||
|
||||
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
|
||||
info = info['info']
|
||||
|
||||
if(info['max_users'] == 0xFFFFFFFF) then
|
||||
info['max_users'] = "<unlimited>"
|
||||
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(" |_ Users: %s, Max: %s\n", info['current_users'], info['max_users'])
|
||||
response = response .. string.format(" |_ Path: %s\n", info['path'])
|
||||
@@ -280,13 +289,14 @@ action = function(host)
|
||||
response = response .. string.format(" %s\n", denied[i])
|
||||
|
||||
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
|
||||
info = info['info']
|
||||
if(info['max_users'] == 0xFFFFFFFF) then
|
||||
info['max_users'] = "<unlimited>"
|
||||
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(" |_ Users: %s, Max: %s\n", info['current_users'], info['max_users'])
|
||||
response = response .. string.format(" |_ Path: %s\n", info['path'])
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
description = [[
|
||||
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,
|
||||
which uses port 445 or 139). Some functions in SAMR are used to enumerate
|
||||
users, and some brute-force guessing using LSA functions is attempted.
|
||||
information as possible, through two different techniques (both over MSRPC,
|
||||
which uses port 445 or 139). Some SAMR functions are used to enumerate users,
|
||||
and bruteforce LSA guessing is attempted.
|
||||
|
||||
One technique used is calling the <code>QueryDisplayInfo</code> function in the SAMR library.
|
||||
If this succeeds, it will return a detailed list of users. 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).
|
||||
By default, both SAMR enumeration and LSA bruteforcing are used; however, these
|
||||
can be fine tuned using Nmap parameters. For the most possible information,
|
||||
leave the defaults; however, there are advantages to using them individually.
|
||||
|
||||
Advantages of using SAMR enumeration:
|
||||
* Stealthier (requires one packet/user account, whereas LSA uses at least 20
|
||||
packets; additionally, LSA makes a lot of noise in the Windows event log (LSA
|
||||
enumeration is the only script I (Ron Bowes) have been called on by the
|
||||
administrator of a box I was testing against).
|
||||
* 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:
|
||||
* <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
|
||||
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
|
||||
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'
|
||||
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
|
||||
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
|
||||
at once causes problems). We continue checking until we reach 1100, and get an empty
|
||||
group. This probably isn't the most effective way, but it seems to work.
|
||||
It might be a good idea to modify this, in the future, with some more
|
||||
To do this, this script breaks users into groups of five RIDs, then checked individually
|
||||
(checking too many at once causes problems). We continue checking until we reach
|
||||
1100, and get an empty group of five. This probably isn't the most effective way, but it
|
||||
seems to work. It might be a good idea to modify this, in the future, with some more
|
||||
intelligence. I (Ron Bowes) performed a test on an old server with a lot of accounts,
|
||||
and 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,
|
||||
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.
|
||||
The SID is determined by doing the reverse operation, that is, converting a name into
|
||||
a RID. The name is determined by looking up any name present on the system.
|
||||
The SID is determined by doing the reverse operation; that is, by converting a name into
|
||||
its RID. The name is determined by looking up any name present on the system.
|
||||
We try:
|
||||
* 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
|
||||
* Some common names: "administrator", "guest", and "test".
|
||||
|
||||
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.
|
||||
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
|
||||
--
|
||||
-- @args smb* This script supports the <code>smbusername</code>,
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||
-- 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"
|
||||
@@ -136,6 +165,7 @@ end
|
||||
--<code>domain</code>, <code>fullname</code>, <code>rid</code>, and
|
||||
--<code>description</code>.
|
||||
local function enum_samr(host)
|
||||
local i, j
|
||||
|
||||
stdnse.print_debug(3, "Entering enum_samr()")
|
||||
|
||||
@@ -177,15 +207,15 @@ local function enum_samr(host)
|
||||
end
|
||||
|
||||
-- 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)
|
||||
return false, "Couldn't find any domains"
|
||||
end
|
||||
|
||||
-- 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
|
||||
if(domain ~= 'Builtin') then
|
||||
local sid
|
||||
@@ -212,24 +242,52 @@ local function enum_samr(host)
|
||||
-- Save the domain handle
|
||||
domain_handle = opendomain_result['domain_handle']
|
||||
|
||||
-- Call QueryDisplayInfo()
|
||||
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, querydisplayinfo_result
|
||||
end
|
||||
-- Loop as long as we're getting valid results
|
||||
j = 0
|
||||
repeat
|
||||
-- Call QueryDisplayInfo()
|
||||
status, querydisplayinfo_result = msrpc.samr_querydisplayinfo(smbstate, domain_handle, j)
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, querydisplayinfo_result
|
||||
end
|
||||
|
||||
-- Save the response
|
||||
if(querydisplayinfo_result['return'] ~= 0 and querydisplayinfo_result['info'] ~= nil and querydisplayinfo_result['info']['entries'] ~= nil and querydisplayinfo_result['info']['entries'][1] ~= nil) then
|
||||
local array = {}
|
||||
local k
|
||||
|
||||
-- The reason these are all indexed from '1' is because we request names one at a time.
|
||||
array['domain'] = domain
|
||||
array['name'] = querydisplayinfo_result['info']['entries'][1]['account_name']
|
||||
array['fullname'] = querydisplayinfo_result['info']['entries'][1]['full_name']
|
||||
array['description'] = querydisplayinfo_result['info']['entries'][1]['description']
|
||||
array['rid'] = querydisplayinfo_result['info']['entries'][1]['rid']
|
||||
array['flags'] = querydisplayinfo_result['info']['entries'][1]['acct_flags']
|
||||
array['source'] = "SAMR Enumeration"
|
||||
|
||||
-- Clean up the 'flags' array
|
||||
for k = 1, #array['flags'], 1 do
|
||||
array['flags'][k] = msrpc.samr_AcctFlags_tostr(array['flags'][k])
|
||||
end
|
||||
|
||||
-- Add it to the array
|
||||
response[#response + 1] = array
|
||||
end
|
||||
j = j + 1
|
||||
until querydisplayinfo_result['return'] == 0
|
||||
|
||||
-- Close the domain handle
|
||||
msrpc.samr_close(smbstate, domain_handle)
|
||||
|
||||
-- Finally, fill in the response!
|
||||
for i = 1, #querydisplayinfo_result['details'], 1 do
|
||||
querydisplayinfo_result['details'][i]['domain'] = domain
|
||||
-- All we get from this is users
|
||||
querydisplayinfo_result['details'][i]['typestr'] = "User"
|
||||
querydisplayinfo_result['details'][i]['source'] = "SAMR Enumeration"
|
||||
response[#response + 1] = querydisplayinfo_result['details'][i]
|
||||
end
|
||||
-- for i = 1, #querydisplayinfo_result['details'], 1 do
|
||||
-- querydisplayinfo_result['details'][i]['domain'] = domain
|
||||
-- -- All we get from this is users
|
||||
-- querydisplayinfo_result['details'][i]['typestr'] = "User"
|
||||
-- querydisplayinfo_result['details'][i]['source'] = "SAMR Enumeration"
|
||||
-- response[#response + 1] = querydisplayinfo_result['details'][i]
|
||||
-- end
|
||||
end -- Checking for 'builtin'
|
||||
end -- Domain loop
|
||||
|
||||
@@ -301,31 +359,30 @@ local function enum_lsa(host)
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, lookupnames2_result
|
||||
end
|
||||
|
||||
-- Loop through the domains returned and find the users in each
|
||||
for i = 1, #lookupnames2_result['domains'], 1 do
|
||||
local domain = lookupnames2_result['domains'][i]['name']
|
||||
local sid = lookupnames2_result['domains'][i]['sid']
|
||||
local rids = { }
|
||||
for i = 1, #lookupnames2_result['domains']['domains'], 1 do
|
||||
local domain = lookupnames2_result['domains']['domains'][i]['name']
|
||||
local sid = lookupnames2_result['domains']['domains'][i]['sid']
|
||||
local sids = { }
|
||||
local start = 1000
|
||||
|
||||
-- Start by looking up 500 - 505 (will likely be Administrator + guest)
|
||||
for j = 500, 505, 1 do
|
||||
rids[#rids + 1] = j
|
||||
sids[#sids + 1] = sid .. "-" .. j
|
||||
end
|
||||
|
||||
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
|
||||
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
|
||||
else
|
||||
-- Put the details for each name into an array
|
||||
for j = 1, #lookupsids2_result['details'], 1 do
|
||||
if(lookupsids2_result['details'][j]['type'] ~= 8) then -- 8 = user not found
|
||||
-- NOTE: Be sure to mirror any changes here in the next bit!
|
||||
for j = 1, #lookupsids2_result['names']['names'], 1 do
|
||||
if(lookupsids2_result['names']['names'][j]['sid_type'] ~= "SID_NAME_UNKNOWN") then
|
||||
local result = {}
|
||||
result['name'] = lookupsids2_result['details'][j]['name']
|
||||
result['rid'] = 500 + j - 1
|
||||
result['domain'] = domain
|
||||
result['typestr'] = lookupsids2_result['details'][j]['typestr']
|
||||
result['name'] = lookupsids2_result['names']['names'][j]['name']
|
||||
result['rid'] = 500 + j - 1
|
||||
result['domain'] = domain
|
||||
result['typestr'] = msrpc.lsa_SidType_tostr(lookupsids2_result['names']['names'][j]['sid_type'])
|
||||
result['source'] = "LSA Bruteforce"
|
||||
response[#response + 1] = result
|
||||
end
|
||||
@@ -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
|
||||
repeat
|
||||
local used_names = 0
|
||||
local rids = {}
|
||||
local sids = {}
|
||||
for j = start, start + 4, 1 do
|
||||
rids[#rids + 1] = j
|
||||
sids[#sids + 1] = sid .. "-" .. j
|
||||
end
|
||||
|
||||
-- Try converting this group of RIDs into names
|
||||
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sid, rids)
|
||||
status, lookupsids2_result = msrpc.lsa_lookupsids2(smbstate, openpolicy2_result['policy_handle'], sids)
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, string.format("Error looking up RIDs: %s", lookupsids2_result))
|
||||
else
|
||||
-- Put the details for each name into an array
|
||||
for j = 1, #lookupsids2_result['details'], 1 do
|
||||
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 = {}
|
||||
result['name'] = lookupsids2_result['details'][j]['name']
|
||||
result['rid'] = start + j - 1
|
||||
result['domain'] = domain
|
||||
result['typestr'] = lookupsids2_result['details'][j]['typestr']
|
||||
result['name'] = lookupsids2_result['names']['names'][j]['name']
|
||||
result['rid'] = start + j - 1
|
||||
result['domain'] = domain
|
||||
result['typestr'] = msrpc.lsa_SidType_tostr(lookupsids2_result['names']['names'][j]['sid_type'])
|
||||
result['source'] = "LSA Bruteforce"
|
||||
response[#response + 1] = result
|
||||
|
||||
-- Increment the number of used names we have
|
||||
used_names = used_names + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -381,38 +435,50 @@ end
|
||||
|
||||
action = function(host)
|
||||
local i, j
|
||||
local samr_status, lsa_status
|
||||
local samr_result, lsa_result
|
||||
local samr_status = false
|
||||
local lsa_status = false
|
||||
local samr_result = "Didn't run"
|
||||
local lsa_result = "Didn't run"
|
||||
local names = {}
|
||||
local name_strings = {}
|
||||
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
|
||||
-- SAMR result to overwrite it.
|
||||
lsa_status, lsa_result = enum_lsa(host)
|
||||
if(lsa_status == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
response = response .. "ERROR: couldn't enum through LSA: " .. lsa_result .. "\n"
|
||||
end
|
||||
else
|
||||
-- Copy the returned array into the names[] table, using the name as the key
|
||||
stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
|
||||
for i = 1, #lsa_result, 1 do
|
||||
names[string.upper(lsa_result[i]['name'])] = lsa_result[i]
|
||||
if(do_lsa) then
|
||||
lsa_status, lsa_result = enum_lsa(host)
|
||||
if(lsa_status == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
response = response .. "ERROR: Couldn't enumerate through LSA: " .. lsa_result .. "\n"
|
||||
end
|
||||
else
|
||||
-- Copy the returned array into the names[] table, using the name as the key
|
||||
stdnse.print_debug(2, "EnumUsers: Received %d names from LSA", #lsa_result)
|
||||
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
|
||||
|
||||
-- Try enumerating through SAMR
|
||||
samr_status, samr_result = enum_samr(host)
|
||||
if(samr_status == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
response = response .. "ERROR: couldn't enumerate through SAMR: " .. samr_result .. "\n"
|
||||
end
|
||||
else
|
||||
-- Copy the returned array into the names[] table, using the name as the key
|
||||
stdnse.print_debug(2, "EnumUsers: Received %d names from SAMR", #samr_result)
|
||||
for i = 1, #samr_result, 1 do
|
||||
names[string.upper(samr_result[i]['name'])] = samr_result[i]
|
||||
if(do_samr) then
|
||||
samr_status, samr_result = enum_samr(host)
|
||||
if(samr_status == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
response = response .. "ERROR: Couldn't enumerate through SAMR: " .. samr_result .. "\n"
|
||||
end
|
||||
else
|
||||
-- Copy the returned array into the names[] table, using the name as the key
|
||||
stdnse.print_debug(2, "EnumUsers: Received %d names from SAMR", #samr_result)
|
||||
for i = 1, #samr_result, 1 do
|
||||
names[string.upper(samr_result[i]['name'])] = samr_result[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -434,7 +500,7 @@ action = function(host)
|
||||
|
||||
-- Check if we actually got any names back
|
||||
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
|
||||
-- If we're not verbose, just print out the names. Otherwise, print out everything we can
|
||||
if(nmap.verbosity() < 1) then
|
||||
@@ -457,7 +523,7 @@ action = function(host)
|
||||
end
|
||||
if(names[name]['fullname'] ~= nil) then response = response .. string.format(" |_ Full name: %s\n", names[name]['fullname']) end
|
||||
if(names[name]['description'] ~= nil) then response = response .. string.format(" |_ Description: %s\n", names[name]['description']) end
|
||||
if(names[name]['flags'] ~= nil) then response = response .. string.format(" |_ Flags: %s\n", stdnse.strjoin(", ", names[name]['flags_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(names[name]['source'] ~= nil) then response = response .. string.format(" |_ Source: %s\n", names[name]['source']) end
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
description = [[
|
||||
Attempts to determine the operating system over the SMB protocol (ports 445 and
|
||||
139).
|
||||
Attempts to determine the operating system, computer name, domain, and current
|
||||
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,
|
||||
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
|
||||
--
|
||||
-- @args smb* This script supports the <code>smbusername</code>,
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||
-- script arguments of the <code>smb</code> module, but they are unlikely
|
||||
-- to be required.
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
author = "Ron Bowes"
|
||||
@@ -92,7 +101,42 @@ action = function(host)
|
||||
-- Kill SMB
|
||||
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'])
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,27 +3,23 @@ Returns information about the SMB security level determined by SMB.
|
||||
|
||||
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
|
||||
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
|
||||
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,
|
||||
it is vulnerable to sniffing.
|
||||
|
||||
Challenge/response passwords supported: If enabled, the server can accept any type of
|
||||
password:
|
||||
* Plaintext
|
||||
* LM and NTLM
|
||||
* LMv2 and NTLMv2
|
||||
If it isn't set, the server can only accept plaintext passwords. Most servers
|
||||
are configured to use challenge/response these days. If a server is configured
|
||||
to accept plaintext passwords, it is vulnerable to sniffing. LM and NTLM are
|
||||
fairly secure, although there are some brute-force attacks against them.
|
||||
|
||||
Message signing: If required, all messages between the client and server must
|
||||
* Challenge/response passwords supported: If enabled, the server can accept any
|
||||
type of password (plaintext, LM and NTLM, and LMv2 and NTLMv2). If it isn't set,
|
||||
the server can only accept plaintext passwords. Most servers are configured to
|
||||
use challenge/response these days. If a server is configured to accept plaintext
|
||||
passwords, it is vulnerable to sniffing. LM and NTLM are fairly secure, although
|
||||
there are some brute-force attacks against them. Additionally, LM and NTLM can
|
||||
fall victim to man-in-the-middle attacks or relay attacks (see MS08-068 or my
|
||||
writeup of it: http://www.skullsecurity.org/blog/?p=110).
|
||||
* 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
|
||||
challenge. If supported and not required, message signing is negotiated between
|
||||
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
|
||||
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
|
||||
attacks.
|
||||
attacks or SMB-relay attacks.
|
||||
|
||||
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.
|
||||
@@ -48,8 +44,8 @@ set the username and password, etc.), but it probably won't ever require them.
|
||||
-- |_ smb-security-mode: Message signing supported
|
||||
--
|
||||
-- @args smb* This script supports the <code>smbusername</code>,
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||
-- script arguments of the <code>smb</code> module.
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
author = "Ron Bowes"
|
||||
|
||||
@@ -7,6 +7,10 @@ of Windows, and Vista doesn't seem to let even the administrator account pull th
|
||||
|
||||
Some of the numbers returned here don't feel right to me, but they're definitely
|
||||
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
|
||||
--
|
||||
-- @args smb* This script supports the <code>smbusername</code>,
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||
-- script arguments of the <code>smb</code> module.
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
author = "Ron Bowes"
|
||||
@@ -80,10 +84,15 @@ action = function(host)
|
||||
smb.stop(smbstate)
|
||||
|
||||
-- Build the response
|
||||
local stats = netservergetstatistics_result['stat']
|
||||
local response = " \n"
|
||||
local period = os.time() - netservergetstatistics_result['start']
|
||||
local period = os.time() - stats['start']
|
||||
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
|
||||
period = 1
|
||||
end
|
||||
@@ -96,9 +105,8 @@ action = function(host)
|
||||
period_str = string.format("%02dm%02ds", period / 60, period % 60)
|
||||
end
|
||||
|
||||
stats = netservergetstatistics_result
|
||||
response = response .. string.format("Server statistics collected since %s (%s):\n", netservergetstatistics_result['start_date'], period_str)
|
||||
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("Server statistics collected since %s (%s):\n", os.date("%Y-%m-%d %H:%M:%S", stats['start']), 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("|_ 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("|_ Print jobs spooled: %s\n", stats['jobsqueued'])
|
||||
|
||||
@@ -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
|
||||
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
|
||||
-- | |_ Microsoft Windows Server 2003 Service Pack 2 (ServerNT 5.2 build 3790)
|
||||
-- | |_ 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\
|
||||
-- | |_ Systemroot: C:\WINDOWS
|
||||
-- | |_ 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)
|
||||
--
|
||||
-- @args smb* This script supports the <code>smbusername</code>,
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, <code>smbguest</code>, and
|
||||
-- <code>smbtype</code> script arguments of the <code>smb</code> module.
|
||||
-- <code>smbpassword</code>, <code>smbhash</code>, and <code>smbtype</code>
|
||||
-- script arguments of the <code>smb</code> module.
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user