description = [[
Check for vulnerabilities:
* MS08-067, a Windows RPC vulnerability
* Conficker, an infection by the Conficker worm
* Unnamed regsvc DoS, a denial-of-service vulnerability I accidentically found in Windows 2000
* SMBv2 exploit (CVE-2009-3103, Microsoft Security Advisory 975497)
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 unsafe
in a production environment! And that isn't to say that non-unsafe scripts will
not crash a system, they're just less likely to.
If you set the script parameter 'safe', then script will run that rarely or never
crash a vulnerable system. No promises, though.
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 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.
At the same time, MS08-067 is extremely critical to fix. Metasploit has a working and
stable exploit for it, and any system vulnerable can very easily be compromised.
Conficker -- Checks if a host is infected with a known Conficker strain. This check
is based on the simple conficker scanner found on this page:
http://iv.cs.uni-bonn.de/wg/cs/applications/containing-conficker
Thanks to the folks who wrote that scanner!
regsvc DoS -- Checks if a host is vulnerable to a crash in regsvc, caused
by a null pointer dereference. I inadvertently discovered this crash while working
on smb-enum-sessions
, and discovered that it was repeatable. It's been
reported to Microsoft (case #MSRC8742).
This check WILL crash the service, if it's vulnerable, and requires a guest account
or higher to work. It is considered unsafe
.
SMBv2 DoS -- performs a denial-of-service against the vulnerability disclosed in
CVE-2009-3103. Checks if the server went offline. This works agianst Windows Vista
and some versions of Windows 7, and causes a bluescreen if successful. The proof-
of-concept code at was used,
with one small change.
(Note: if you have other SMB/MSRPC vulnerability checks you'd like to see added, and
you can show me a tool with a license that is compatible with Nmap's, post a request
on the Nmap-dev mailing list and I'll add it to my list [Ron Bowes]).
]]
---
--@usage
-- nmap --script smb-check-vulns.nse -p445
-- sudo nmap -sU -sS --script smb-check-vulns.nse -p U:137,T:139
--
--@output
-- Host script results:
-- | smb-check-vulns:
-- | | MS08-067: NOT VULNERABLE
-- | | Conficker: Likely CLEAN
-- | | regsvc DoS: NOT VULNERABLE
-- |_ |_ SMBv2 DoS (CVE-2009-3103): NOT VULNERABLE
--
-- @args unsafe If set, this script will run checks that, if the system isn't
-- patched, are basically guaranteed to crash something. Remember that
-- non-unsafe checks aren't necessarily safe either)
-- @args safe If set, this script will only run checks that are known (or at
-- least suspected) to be safe.
-----------------------------------------------------------------------
author = "Ron Bowes"
copyright = "Ron Bowes"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"intrusive","exploit","dos","vuln"}
-- run after all smb-* scripts (so if it DOES crash something, it doesn't kill
-- other scans have had a chance to run)
dependencies = {
"smb-brute", "smb-enum-sessions", "smb-security-mode",
"smb-enum-shares", "smb-server-stats",
"smb-enum-domains", "smb-enum-users", "smb-system-info",
"smb-enum-groups", "smb-os-discovery", "smb-enum-processes",
"smb-psexec",
};
require 'msrpc'
require 'smb'
require 'stdnse'
hostrule = function(host)
return smb.get_port(host) ~= nil
end
local VULNERABLE = 1
local PATCHED = 2
local UNKNOWN = 3
local NOTRUN = 4
local INFECTED = 5
local INFECTED2 = 6
local CLEAN = 7
---Check if the server is patched for MS08-067. This is done by calling NetPathCompare with an
-- illegal string. If the string is accepted, then the server is vulnerable; if it's rejected, then
-- you're safe (for now).
--
-- Based on a packet cap of this script, thanks go out to the author:
-- http://labs.portcullis.co.uk/download/ms08-067_check.py
--
-- If there's a licensing issue, please let me (Ron Bowes) know so I can
--
-- NOTE: This CAN crash stuff (ie, crash svchost and force a reboot), so beware! In about 20
-- tests I did, it crashed once. This is not a guarantee.
--
--@param host The host object.
--@return (status, result) If status is false, result is an error code; otherwise, result is either
-- VULNERABLE
for vulnerable, PATCHED
for not vulnerable,
-- UNKNOWN
if there was an error (likely vulnerable), NOTRUN
-- if this check was disabled, and INFECTED
if it was patched by Conficker.
function check_ms08_067(host)
if(nmap.registry.args.safe ~= nil) then
return true, NOTRUN
end
local status, smbstate
local bind_result, netpathcompare_result
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, "\\\\BROWSER")
if(status == false) then
return false, smbstate
end
-- Bind to SRVSVC service
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Call netpathcanonicalize
-- status, netpathcanonicalize_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\a", "\\test\\")
local path1 = "\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\..\\n"
local path2 = "\\n"
status, netpathcompare_result = msrpc.srvsvc_netpathcompare(smbstate, host.ip, path1, path2, 1, 0)
-- Stop the SMB session
msrpc.stop_smb(smbstate)
if(status == false) then
if(string.find(netpathcompare_result, "UNKNOWN_57") ~= nil) then
return true, INFECTED
elseif(string.find(netpathcompare_result, "INVALID_NAME") ~= nil) then
return true, PATCHED
else
return true, UNKNOWN, netpathcompare_result
end
end
return true, VULNERABLE
end
-- Help messages for the more common errors seen by the Conficker check.
CONFICKER_ERROR_HELP = {
["NT_STATUS_BAD_NETWORK_NAME"] =
[[UNKNOWN; Network name not found (required service has crashed). (Error NT_STATUS_BAD_NETWORK_NAME)]],
-- http://seclists.org/nmap-dev/2009/q1/0918.html "non-Windows boxes (Samba on Linux/OS X, or a printer)"
-- http://www.skullsecurity.org/blog/?p=209#comment-156
-- "That means either it isn’t a Windows machine, or the service is
-- either crashed or not running. That may indicate a failed (or
-- successful) exploit attempt, or just a locked down system.
-- NT_STATUS_OBJECT_NAME_NOT_FOUND can be returned if the browser
-- service is disabled. There are at least two ways that can happen:
-- 1) The service itself is disabled in the services list.
-- 2) The registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Browser\Parameters\MaintainServerList
-- is set to Off/False/No rather than Auto or yes.
-- On these systems, if you reenable the browser service, then the
-- test will complete."
["NT_STATUS_OBJECT_NAME_NOT_FOUND"] =
[[UNKNOWN; not Windows, or Windows with disabled browser service (CLEAN); or Windows with crashed browser service (possibly INFECTED).
| If you know the remote system is Windows, try rebooting it and scanning
|_ again. (Error NT_STATUS_OBJECT_NAME_NOT_FOUND)]],
-- http://www.skullsecurity.org/blog/?p=209#comment-100
-- "That likely means that the server has been locked down, so we
-- don’t have access to the necessary pipe. Fortunately, that means
-- that neither does Conficker — NT_STATUS_ACCESS_DENIED probably
-- means you’re ok."
["NT_STATUS_ACCESS_DENIED"] =
[[Likely CLEAN; access was denied.
| If you have a login, try using --script-args=smbuser=xxx,smbpass=yyy
| (replace xxx and yyy with your username and password). Also try
|_ smbdomain=zzz if you know the domain. (Error NT_STATUS_ACCESS_DENIED)]],
-- The cause of these two is still unknown.
-- ["NT_STATUS_NOT_SUPPORTED"] =
-- [[]]
-- http://thatsbroken.com/?cat=5 (doesn't seem common)
-- ["NT_STATUS_REQUEST_NOT_ACCEPTED"] =
-- [[]]
}
---Check if the server is infected with Conficker. This can be detected by a modified MS08-067 patch,
-- which rejects a different illegal string than the official patch rejects.
--
-- Based loosely on the Simple Conficker Scanner, found here:
-- http://iv.cs.uni-bonn.de/wg/cs/applications/containing-conficker/
--
-- If there's a licensing issue, please let me (Ron Bowes) know so I can fix it
--
--@param host The host object.
--@return (status, result) If status is false, result is an error code; otherwise, result is either
-- INFECTED
for infected or CLEAN
for not infected.
function check_conficker(host)
local status, smbstate
local bind_result, netpathcompare_result
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, "\\\\BROWSER", true)
if(status == false) then
return false, smbstate
end
-- Bind to SRVSVC service
status, bind_result = msrpc.bind(smbstate, msrpc.SRVSVC_UUID, msrpc.SRVSVC_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
-- Try checking a valid string to find Conficker.D
status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\")
if(status == true and netpathcanonicalize_result['can_path'] == 0x5c45005c) then
msrpc.stop_smb(smbstate)
return true, INFECTED2
end
-- Try checking an illegal string ("\..\") to find Conficker.C and earlier
local error_result
status, netpathcanonicalize_result, error_result = msrpc.srvsvc_netpathcanonicalize(smbstate, host.ip, "\\..\\")
if(status == false) then
if(string.find(netpathcanonicalize_result, "INVALID_NAME")) then
msrpc.stop_smb(smbstate)
return true, CLEAN
elseif(string.find(netpathcanonicalize_result, "UNKNOWN_57") ~= nil) then
msrpc.stop_smb(smbstate)
return true, INFECTED
else
msrpc.stop_smb(smbstate)
return false, netpathcanonicalize_result
end
end
-- Stop the SMB session
msrpc.stop_smb(smbstate)
return true, CLEAN
end
---While writing smb-enum-sessions
I discovered a repeatable null-pointer dereference
-- in regsvc. I reported it to Microsoft, but because it's a simple DoS (and barely even that, because
-- the service automatically restarts), and because it's only in Windows 2000, it isn't likely that they'll
-- fix it. This function checks for that crash (by crashing the process).
--
-- The crash occurs when the string sent to winreg_enumkey() function is null.
--
--@param host The host object.
--@return (status, result) If status is false, result is an error code; otherwise, result is either
-- VULNERABLE
for vulnerable or PATCHED
for not vulnerable. If the check
-- was skipped, NOTRUN
is returned.
function check_winreg_Enum_crash(host)
if(nmap.registry.args.safe ~= nil) then
return true, NOTRUN
end
if(nmap.registry.args.unsafe == nil) then
return true, NOTRUN
end
local i, j
local elements = {}
-- Create the SMB session
status, smbstate = msrpc.start_smb(host, msrpc.WINREG_PATH)
if(status == false) then
return false, smbstate
end
-- Bind to WINREG service
status, bind_result = msrpc.bind(smbstate, msrpc.WINREG_UUID, msrpc.WINREG_VERSION, nil)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, bind_result
end
status, openhku_result = msrpc.winreg_openhku(smbstate)
if(status == false) then
msrpc.stop_smb(smbstate)
return false, openhku_result
end
-- Loop through the keys under HKEY_USERS and grab the names
status, enumkey_result = msrpc.winreg_enumkey(smbstate, openhku_result['handle'], 0, nil)
msrpc.stop_smb(smbstate)
if(status == false) then
return true, VULNERABLE
end
return true, PATCHED
end
local function check_smbv2_dos(host)
local status, result
if(nmap.registry.args.safe ~= nil) then
return true, NOTRUN
end
if(nmap.registry.args.unsafe == nil) then
return true, NOTRUN
end
-- From http://seclists.org/fulldisclosure/2009/Sep/0039.html with one change on the last line.
local buf = string.char(0x00, 0x00, 0x00, 0x90) .. -- Begin SMB header: Session message
string.char(0xff, 0x53, 0x4d, 0x42) .. -- Server Component: SMB
string.char(0x72, 0x00, 0x00, 0x00) .. -- Negociate Protocol
string.char(0x00, 0x18, 0x53, 0xc8) .. -- Operation 0x18 & sub 0xc853
string.char(0x00, 0x26) .. -- Process ID High: --> :) normal value should be ", 0x00, 0x00"
string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfe) ..
string.char(0x00, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4e, 0x45, 0x54) ..
string.char(0x57, 0x4f, 0x52, 0x4b, 0x20, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x20, 0x31) ..
string.char(0x2e, 0x30, 0x00, 0x02, 0x4c, 0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x31, 0x2e, 0x30, 0x00) ..
string.char(0x02, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x57) ..
string.char(0x6f, 0x72, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x20, 0x33, 0x2e, 0x31, 0x61) ..
string.char(0x00, 0x02, 0x4c, 0x4d, 0x31, 0x2e, 0x32, 0x58, 0x30, 0x30, 0x32, 0x00, 0x02, 0x4c) ..
string.char(0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x32, 0x2e, 0x31, 0x00, 0x02, 0x4e, 0x54, 0x20, 0x4c) ..
string.char(0x4d, 0x20, 0x30, 0x2e, 0x31, 0x32, 0x00, 0x02, 0x53, 0x4d, 0x42, 0x20, 0x32, 0x2e) ..
string.char(0x30, 0x30, 0x32, 0x00)
local socket = nmap:new_socket()
if(socket == nil) then
return false, "Couldn't create socket"
end
status, result = socket:connect(host.ip, 445)
if(status == false) then
socket:close()
return false, "Couldn't connect to host: " .. result
end
status, result = socket:send(buf)
if(status == false) then
socket:close()
return false, "Couldn't send the buffer: " .. result
end
-- Close the socket
socket:close()
-- Give it some time to crash
stdnse.print_debug(1, "smb-check-vulns: Waiting 5 seconds to see if Windows crashed")
stdnse.sleep(5)
-- Create a new socket
socket = nmap:new_socket()
if(socket == nil) then
return false, "Couldn't create socket"
end
-- Try and do something simple
stdnse.print_debug(1, "smb-check-vulns: Attempting to connect to the host")
socket:set_timeout(5000)
status, result = socket:connect(host.ip, 445)
-- Check the result
if(status == false or status == nil) then
stdnse.print_debug(1, "smb-check-vulns: Connect failed, host is likely vulnerable!")
socket:close()
return true, VULNERABLE
end
-- Try sending something
stdnse.print_debug(1, "smb-check-vulns: Attempting to send data to the host")
status, result = socket:send("AAAA")
if(status == false or status == nil) then
stdnse.print_debug(1, "smb-check-vulns: Send failed, host is likely vulnerable!")
socket:close()
return true, VULNERABLE
end
stdnse.print_debug(1, "smb-check-vulns: Checks finished; host is likely not vulnerable.")
socket:close()
return true, PATCHED
end
---Returns the appropriate text to display, if any.
--
--@param check The name of the check; for example, 'ms08-067'.
--@param message The message to display, such as 'VULNERABLE' or 'PATCHED'.
--@param description [optional] Extra details about the message. nil for a blank message.
--@param minimum_verbosity The minimum verbosity level required before the message is displayed.
--@param minimum_debug [optional] The minimum debug level required before the message is displayed (default: 0).
--@return A string with a textual representation of the error (or empty string, if it was determined that the message shouldn't be displayed).
local function get_response(check, message, description, minimum_verbosity, minimum_debug)
if(minimum_debug == nil) then
minimum_debug = 0
end
-- Check if we have appropriate verbosity/debug
if(nmap.verbosity() >= minimum_verbosity and nmap.debugging() >= minimum_debug) then
if(description == nil or description == '') then
return string.format("%s: %s", check, message)
else
return string.format("%s: %s (%s)", check, message, description)
end
else
return ''
end
end
action = function(host)
local status, result, message
local response = {}
-- Check for ms08-067
status, result, message = check_ms08_067(host)
if(status == false) then
table.insert(response, get_response("MS08-067", "ERROR", result, 0, 1))
else
if(result == VULNERABLE) then
table.insert(response, get_response("MS08-067", "VULNERABLE", nil, 0))
elseif(result == UNKNOWN) then
table.insert(response, get_response("MS08-067", "LIKELY VULNERABLE", "host stopped responding", 1)) -- TODO: this isn't very accurate
elseif(result == NOTRUN) then
table.insert(response, get_response("MS08-067", "CHECK DISABLED", "remove 'safe=1' argument to run", 1))
elseif(result == INFECTED) then
table.insert(response, get_response("MS08-067", "NOT VULNERABLE", "likely by Conficker", 0))
else
table.insert(response, get_response("MS08-067", "NOT VULNERABLE", nil, 1))
end
end
-- Check for Conficker
status, result = check_conficker(host)
if(status == false) then
local msg = CONFICKER_ERROR_HELP[result] or "UNKNOWN; got error " .. result
table.insert(response, get_response("Conficker", msg, nil, 1)) -- Only set verbosity for this, since it might be an error or it might be UNKNOWN
else
if(result == CLEAN) then
table.insert(response, get_response("Conficker", "Likely CLEAN", nil, 1))
elseif(result == INFECTED) then
table.insert(response, get_response("Conficker", "Likely INFECTED", "by Conficker.C or lower", 0))
elseif(result == INFECTED2) then
table.insert(response, get_response("Conficker", "Likely INFECTED", "by Conficker.D or higher", 0))
else
table.insert(response, get_response("Conficker", "UNKNOWN", result, 0, 1))
end
end
-- Check for a winreg_Enum crash
status, result = check_winreg_Enum_crash(host)
if(status == false) then
table.insert(response, get_response("regsvc DoS", "ERROR", result, 0, 1))
else
if(result == VULNERABLE) then
table.insert(response, get_response("regsvc DoS", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("regsvc DoS", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
else
table.insert(response, get_response("regsvc DoS", "NOT VULNERABLE", nil, 1))
end
end
-- Check for SMBv2 vulnerablity
status, result = check_smbv2_dos(host)
if(status == false) then
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "ERROR", result, 0, 1))
else
if(result == VULNERABLE) then
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "VULNERABLE", nil, 0))
elseif(result == NOTRUN) then
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "CHECK DISABLED", "add '--script-args=unsafe=1' to run", 1))
else
table.insert(response, get_response("SMBv2 DoS (CVE-2009-3103)", "NOT VULNERABLE", nil, 1))
end
end
return stdnse.format_output(true, response)
end