diff --git a/docs/scripting.xml b/docs/scripting.xml index 015766c15..6c319cb11 100644 --- a/docs/scripting.xml +++ b/docs/scripting.xml @@ -875,22 +875,64 @@ that. - - <literal>runlevel</literal> Field - runlevel” script variable - run level of scripts - - This optional field determines script execution order. When - this section is absent, the run level defaults to 1.0. Scripts with a given runlevel execute after any with a lower runlevel and before any scripts with a higher runlevel against a single target machine. The order of scripts with the same runlevel is undefined and they often run concurrently. One - application of run levels is allowing scripts to depend on - each other. If script A relies on some - information gathered by script B, give - B a lower run level than - A. Script B can store - information in the NSE registry for A to - retrieve later. For information on the NSE registry, see - . - + + <literal>dependencies</literal> Field + dependencies” script variable + script dependencies + + In earlier versions of NSE, script authors were able to specify a + runlevel that would specify the execution order of + the scripts NSE will run. Scripts that had a smaller runlevel would + run before scripts with a larger runlevel. Scripts with an equal + runlevel would run concurrently. This method of describing an ordered + execution has been replaced by dependencies. + Dependencies specify other discrete scripts that the script depends on + for its execution. A script may need to depend on another script for + many reasons. For example, many scripts may rely on authentication + credentials discovered by brute-forcing scripts. + + + Scripts may specify an array of script names that the script depends + on. When we say "depends on", we mean it in a loose sense. That is, a + script will still run despite missing dependencies. Given the + dependencies, the script will run after all the scripts listed in the + dependencies array. We may specify a dependencies array like so: + +dependencies = {"script1", "script2"} + + + + The dependencies table is an optional script field. NSE will assume + the script has no dependencies if the field is omitted. + + + Dependencies offer many advantages over runlevels. First, and + obviously, scripts can now specify each script they depend on without + worrying about specifying an arbitrary number that is greater than + scripts it depends on. Second, scripts no longer limit NSE's ability + to intelligently schedule scripts to maximize parallelism. Having + unique runlevels would force NSE to schedule the scripts to execute + serially. + + + Runlevels are still used as an internal representation of the order of + scripts that are automatically generated by the dependencies. When + running your scripts you will see each runlevel (and the number of + runlevels) grouping of scripts run in NSE's output: + +NSE: Script scanning 127.0.0.1. +NSE: Starting runlevel 1 (of 3) scan. +Initiating NSE at 17:38 +Completed NSE at 17:38, 0.00s elapsed +NSE: Starting runlevel 2 (of 3) scan. +Initiating NSE at 17:38 +Completed NSE at 17:38, 0.00s elapsed +NSE: Starting runlevel 3 (of 3) scan. +Initiating NSE at 17:38 +Completed NSE at 17:38, 0.00s elapsed +NSE: Script Scanning completed. + + diff --git a/nse_main.lua b/nse_main.lua index 19b405f72..ad78196ba 100644 --- a/nse_main.lua +++ b/nse_main.lua @@ -67,6 +67,8 @@ local yield = coroutine.yield; local traceback = debug.traceback; +local max = math.max; + local byte = string.byte; local find = string.find; local format = string.format; @@ -75,6 +77,7 @@ local lower = string.lower; local match = string.match; local sub = string.sub; +local concat = table.concat; local insert = table.insert; local remove = table.remove; local sort = table.sort; @@ -205,7 +208,6 @@ do if not self[rule] then return nil end -- No rule for this script? local file_closure = self.file_closure; local env = setmetatable({ - runlevel = 1, filename = self.filename, }, {__index = _G}); setfenv(file_closure, env); @@ -227,7 +229,6 @@ do local thread = setmetatable({ co = co, env = env, - runlevel = tonumber(rawget(env, "runlevel")) or 1, identifier = tostring(co), info = format("'%s' (%s)", self.short_basename, tostring(co)); type = rule == "hostrule" and "host" or "port", @@ -249,6 +250,7 @@ do description = "string", action = "function", categories = "table", + dependencies = "table", }; -- script = Script.new(filename) -- Creates a new Script Class for the script. @@ -267,7 +269,7 @@ do -- Give the closure its own environment, with global access local env = setmetatable({ filename = filename, - runlevel = 1, + dependencies = {}, }, {__index = _G}); setfenv(file_closure, env); local co = create(file_closure); -- Create a garbage thread @@ -291,6 +293,11 @@ do assert(type(category) == "string", filename.." has non-string entries in the 'categories' array"); end + -- Assert that dependencies is an array of strings + for i, dependency in ipairs(rawget(env, "dependencies")) do + assert(type(dependency) == "string", + filename.." has non-string entries in the 'dependencies' array"); + end -- Return the script return setmetatable({ filename = filename, @@ -306,7 +313,7 @@ do categories = rawget(env, "categories"), author = rawget(env, "author"), license = rawget(env, "license"), - runlevel = tonumber(rawget(env, "runlevel")) or 1, + dependencies = rawget(env, "dependencies"), threads = {}, selected_by_name = false, }, {__index = Script, __metatable = Script}); @@ -471,6 +478,38 @@ local function get_chosen_scripts (rules) end end end + + -- calculate runlevels + local name_script = {}; + for i, script in ipairs(chosen_scripts) do + assert(name_script[script.short_basename] == nil); + name_script[script.short_basename] = script; + end + local chain = {}; -- chain of script names + local function calculate_runlevel (script) + chain[#chain+1] = script.short_basename; + if script.runlevel == false then -- circular dependency + error("circular dependency in chain `"..concat(chain, "->").."`"); + else + script.runlevel = false; -- placeholder + end + local runlevel = 1; + for i, dependency in ipairs(script.dependencies) do + -- yes, use rawget in case we add strong dependencies again + local s = rawget(name_script, dependency); + if s then + local r = tonumber(s.runlevel) or calculate_runlevel(s); + runlevel = max(runlevel, r+1); + end + end + chain[#chain] = nil; + script.runlevel = runlevel; + return runlevel; + end + for i, script in ipairs(chosen_scripts) do + local _ = script.runlevel or calculate_runlevel(script); + end + return chosen_scripts; end @@ -776,7 +815,8 @@ return function (hosts) sort(runlevels); for i, runlevel in ipairs(runlevels) do - print_verbose(1, "Starting runlevel %g scan", runlevel); + print_verbose(1, "Starting runlevel %u (of %u) scan.", runlevel, + #runlevels); run(threads[runlevel]); end diff --git a/scripts/asn-query.nse b/scripts/asn-query.nse index f9f03587a..59de9365c 100644 --- a/scripts/asn-query.nse +++ b/scripts/asn-query.nse @@ -37,8 +37,6 @@ server (your default DNS server, or whichever one you specified with the author = "jah, Michael" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "external", "safe"} -runlevel = 1 - local dns = require "dns" diff --git a/scripts/banner.nse b/scripts/banner.nse index 671ca2086..9736c1b1d 100644 --- a/scripts/banner.nse +++ b/scripts/banner.nse @@ -13,7 +13,6 @@ increase in the level of verbosity requested on the command line. author = "jah " license = "See Nmap License: http://nmap.org/book/man-legal.html" -runlevel = 1 categories = {"discovery", "safe"} diff --git a/scripts/dns-zone-transfer.nse b/scripts/dns-zone-transfer.nse index f4780a6df..53791f7ea 100644 --- a/scripts/dns-zone-transfer.nse +++ b/scripts/dns-zone-transfer.nse @@ -59,7 +59,6 @@ require('dns') author = 'Eddie Bell' license = 'Same as Nmap--See http://nmap.org/book/man-legal.html' categories = {'default', 'intrusive', 'discovery'} -runlevel = 1.0 portrule = shortport.portnumber(53, 'tcp') diff --git a/scripts/p2p-conficker.nse b/scripts/p2p-conficker.nse index 75b317d2d..5ce27540f 100644 --- a/scripts/p2p-conficker.nse +++ b/scripts/p2p-conficker.nse @@ -67,9 +67,6 @@ author = "Ron Bowes (with research from Symantec Security Response)" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default","safe"} --- Set the runlevel to 2. This means this script will run last, but it will also run in parallel with smb-check-vulns.nse, --- which will generally be run at the same time. So, by setting this to 2, we increase our parallelism. -runlevel = 2 require 'smb' require 'stdnse' diff --git a/scripts/robots.txt.nse b/scripts/robots.txt.nse index 022081e1a..7905b226b 100644 --- a/scripts/robots.txt.nse +++ b/scripts/robots.txt.nse @@ -24,7 +24,6 @@ require('http') author = "Eddie Bell" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default", "discovery", "safe"} -runlevel = 1.0 portrule = shortport.port_or_service({80, 8080,443}, {"http","https"}) local last_len = 0 diff --git a/scripts/smb-brute.nse b/scripts/smb-brute.nse index b724fc6c5..515c8518c 100644 --- a/scripts/smb-brute.nse +++ b/scripts/smb-brute.nse @@ -33,7 +33,7 @@ When an account is discovered, it's saved in the smb module (which registry). If an account is already saved, the account's privileges are checked; accounts with administrator privileges are kept over accounts without. The specific method for checking is by calling GetShareInfo("IPC$"), which requires administrative privileges. Once this script -is finished (since it's runlevel 0.5, it'll run first), other scripts will use the saved account +is finished (all other smb scripts depend on it, it'll run first), other scripts will use the saved account to perform their checks. The blank password is always tried first, followed by "special passwords" (such as the username @@ -95,8 +95,6 @@ determined with a fairly efficient bruteforce. For example, if the actual passwo author = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" --- Set the runlevel to <1 to ensure that it runs before other scripts -runlevel = 0.5 categories = {"intrusive", "auth"} diff --git a/scripts/smb-check-vulns.nse b/scripts/smb-check-vulns.nse index 9b56456b8..720a79efc 100644 --- a/scripts/smb-check-vulns.nse +++ b/scripts/smb-check-vulns.nse @@ -79,9 +79,15 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"intrusive","exploit","dos","vuln"} --- Set the runlevel to >2 so this runs last (so if it DOES crash something, it doesn't --- till other scans have had a chance to run) -runlevel = 2 +-- run after all smb-* scripts (so if it DOES crash something, it doesn't till +-- other scans have had a chance to run) +dependencies = { + "smb-brute", smb-enum-sessions", "smb-security-mode", + "smb-check-vulns", "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' diff --git a/scripts/smb-enum-domains.nse b/scripts/smb-enum-domains.nse index 38ecd78fc..5024a24d6 100644 --- a/scripts/smb-enum-domains.nse +++ b/scripts/smb-enum-domains.nse @@ -52,6 +52,7 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery","intrusive"} +dependencies = {"smb-brute"} require 'msrpc' require 'smb' diff --git a/scripts/smb-enum-groups.nse b/scripts/smb-enum-groups.nse index b107ced0a..4826fd9c0 100644 --- a/scripts/smb-enum-groups.nse +++ b/scripts/smb-enum-groups.nse @@ -57,6 +57,7 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery","intrusive"} +dependencies = {"smb-brute"} require 'msrpc' require 'smb' diff --git a/scripts/smb-enum-processes.nse b/scripts/smb-enum-processes.nse index b0fb70594..6d58b740e 100644 --- a/scripts/smb-enum-processes.nse +++ b/scripts/smb-enum-processes.nse @@ -76,6 +76,7 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "intrusive"} +dependencies = {"smb-brute"} require "bin" require "msrpc" diff --git a/scripts/smb-enum-sessions.nse b/scripts/smb-enum-sessions.nse index f09db91a1..777f99150 100644 --- a/scripts/smb-enum-sessions.nse +++ b/scripts/smb-enum-sessions.nse @@ -58,6 +58,7 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery","intrusive"} +dependencies = {"smb-brute"} require 'msrpc' require 'smb' diff --git a/scripts/smb-enum-shares.nse b/scripts/smb-enum-shares.nse index b65c5d741..77879f892 100644 --- a/scripts/smb-enum-shares.nse +++ b/scripts/smb-enum-shares.nse @@ -57,6 +57,7 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery","intrusive"} +dependencies = {"smb-brute"} require 'msrpc' require 'smb' diff --git a/scripts/smb-enum-users.nse b/scripts/smb-enum-users.nse index 92b94444e..8924d616f 100644 --- a/scripts/smb-enum-users.nse +++ b/scripts/smb-enum-users.nse @@ -136,6 +136,7 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery","intrusive"} +dependencies = {"smb-brute"} require 'msrpc' require 'smb' diff --git a/scripts/smb-os-discovery.nse b/scripts/smb-os-discovery.nse index 5175ff422..7412bdb92 100644 --- a/scripts/smb-os-discovery.nse +++ b/scripts/smb-os-discovery.nse @@ -37,6 +37,7 @@ they likely won't change the outcome in any meaningful way. author = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default", "discovery", "safe"} +dependencies = {"smb-brute"} require 'smb' require 'stdnse' diff --git a/scripts/smb-psexec.nse b/scripts/smb-psexec.nse index 991f9236c..84b96ff81 100644 --- a/scripts/smb-psexec.nse +++ b/scripts/smb-psexec.nse @@ -406,6 +406,7 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"intrusive"} +dependencies = {"smb-brute"} require 'bit' require 'msrpc' diff --git a/scripts/smb-security-mode.nse b/scripts/smb-security-mode.nse index 951daf9b7..919559172 100644 --- a/scripts/smb-security-mode.nse +++ b/scripts/smb-security-mode.nse @@ -29,6 +29,7 @@ set the username and password, etc.), but it probably won't ever require them. author = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "safe"} +dependencies = {"smb-brute"} require 'smb' require 'stdnse' diff --git a/scripts/smb-server-stats.nse b/scripts/smb-server-stats.nse index 2393bb479..f9f2dd20c 100644 --- a/scripts/smb-server-stats.nse +++ b/scripts/smb-server-stats.nse @@ -30,6 +30,7 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery","intrusive"} +dependencies = {"smb-brute"} require 'msrpc' require 'smb' diff --git a/scripts/smb-system-info.nse b/scripts/smb-system-info.nse index 5cce205a5..3260d3c0e 100644 --- a/scripts/smb-system-info.nse +++ b/scripts/smb-system-info.nse @@ -48,6 +48,7 @@ author = "Ron Bowes" copyright = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery","intrusive"} +dependencies = {"smb-brute"} require 'msrpc' require 'smb' diff --git a/scripts/snmp-brute.nse b/scripts/snmp-brute.nse index 469c6e1d0..12df738b4 100644 --- a/scripts/snmp-brute.nse +++ b/scripts/snmp-brute.nse @@ -17,9 +17,6 @@ categories = {"intrusive", "auth"} require "shortport" require "snmp" --- runs before snmp-sysdescr.nse -runlevel = 1 - portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) action = function(host, port) diff --git a/scripts/snmp-sysdescr.nse b/scripts/snmp-sysdescr.nse index 2b13906cd..4adefb9db 100644 --- a/scripts/snmp-sysdescr.nse +++ b/scripts/snmp-sysdescr.nse @@ -13,12 +13,11 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default", "discovery", "safe"} +dependencies = {"snmp-brute"} + require "shortport" require "snmp" --- runs after snmp-brute.nse -runlevel = 2 - portrule = shortport.portnumber(161, "udp", {"open", "open|filtered"}) --- diff --git a/scripts/sql-injection.nse b/scripts/sql-injection.nse index 0fe6a3fde..940dc1e4b 100644 --- a/scripts/sql-injection.nse +++ b/scripts/sql-injection.nse @@ -25,7 +25,6 @@ require('nsedebug') author = "Eddie Bell" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"intrusive", "vuln"} -runlevel = 1.0 -- Change this to increase depth of crawl local maxdepth = 10 diff --git a/scripts/whois.nse b/scripts/whois.nse index a860bdef0..5aeaa9a5e 100644 --- a/scripts/whois.nse +++ b/scripts/whois.nse @@ -75,7 +75,6 @@ the RIRs. author = "jah " license = "See Nmap License: http://nmap.org/book/man-legal.html" -runlevel = 1 categories = {"discovery", "external", "safe"} local url = require "url"