diff --git a/CHANGELOG b/CHANGELOG index bc0925de4..9df5973e3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -251,6 +251,13 @@ o [NSE] Nmap has two new NSE script scanning phases. The new pre-scan found to be running each service. See http://nmap.org/book/nse-usage.html#nse-script-types. [Djalal] +o A new --script-help option prints out the categories, NSEDoc URL, + and description for scripts matching a specification. Use it like + this for example: nmap --script-help "default or http-*". You can + use this to get a preview of what scripts will run for a given + specification. Martin Holst Swende had the idea for this feature and + wrote the first implementation. [David] + o Dramatically improved nmap.xsl (used for converting Nmap XML output to HTML). In particular: - Put verbose details behind expander buttons so you can see them if diff --git a/NmapOps.cc b/NmapOps.cc index a1eaac996..40054859d 100644 --- a/NmapOps.cc +++ b/NmapOps.cc @@ -308,6 +308,7 @@ void NmapOps::Initialize() { scriptversion = 0; scripttrace = 0; scriptupdatedb = 0; + scripthelp = false; chosenScripts.clear(); #endif memset(&sourcesock, 0, sizeof(sourcesock)); diff --git a/NmapOps.h b/NmapOps.h index ef84dc3ac..43b5ada0b 100644 --- a/NmapOps.h +++ b/NmapOps.h @@ -326,6 +326,7 @@ class NmapOps { int scriptversion; int scripttrace; int scriptupdatedb; + bool scripthelp; void chooseScripts(char* argument); std::vector chosenScripts; #endif diff --git a/docs/nse-scripts.dtd b/docs/nse-scripts.dtd new file mode 100644 index 000000000..9847060e6 --- /dev/null +++ b/docs/nse-scripts.dtd @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/docs/scripting.xml b/docs/scripting.xml index f4a67700f..a969f3ac7 100644 --- a/docs/scripting.xml +++ b/docs/scripting.xml @@ -192,7 +192,10 @@ Black Hat Briefings in 2010. directories full of scripts you wish to execute. You can customize some scripts by providing arguments to them via the - option. The two remaining options, + option. + The + shows a description of what each selected script does. + The two remaining options, and , are generally only used for script debugging and development. Script scanning is also included as part of the (aggressive scan) option. @@ -664,6 +667,51 @@ Nmap script database, but should be used cautiously since Nmap may contain explo for a detailed explanation. + + + + + + + + + Shows help about scripts. For each script matching the given + specification, Nmap prints the script name, its categories, and its + description. The specifications are the same as those accepted by + ; so for example if you want help about + the ftp-anon script, you would run + nmap --script-help ftp-anon. A sample of script + help is shown in . + + + example of + Script help + +$ nmap --script-help "afp-* and discovery" + +Starting Nmap 5.36TEST4 ( http://nmap.org ) at 2011-01-27 13:04 PST + +afp-serverinfo +Categories: discovery safe +http://nmap.org/nsedoc/scripts/afp-serverinfo.html + Shows AFP server information. This information includes the server's + hostname, IPv4 and IPv6 addresses, and hardware type (for example + Macmini or MacBookPro). + +afp-showmount +Categories: discovery safe +http://nmap.org/nsedoc/scripts/afp-showmount.html + Shows AFP shares and ACLs. + + + If the + + option is used, an XML representation of the script help will be + written to the given file. + + + + diff --git a/nmap.cc b/nmap.cc index 554ea9aa0..a8055d558 100644 --- a/nmap.cc +++ b/nmap.cc @@ -602,6 +602,8 @@ int nmap_main(int argc, char *argv[]) { {"script_updatedb", no_argument, 0, 0}, {"script-args",required_argument,0,0}, {"script_args",required_argument,0,0}, + {"script-help",required_argument,0,0}, + {"script_help",required_argument,0,0}, #endif {"ip_options", required_argument, 0, 0}, {"ip-options", required_argument, 0, 0}, @@ -647,6 +649,9 @@ int nmap_main(int argc, char *argv[]) { o.scripttrace = 1; } else if (optcmp(long_options[option_index].name, "script-updatedb") == 0){ o.scriptupdatedb = 1; + } else if (optcmp(long_options[option_index].name, "script-help") == 0){ + o.scripthelp = true; + o.chooseScripts(optarg); } else #endif if (optcmp(long_options[option_index].name, "max-os-tries") == 0) { @@ -1316,6 +1321,11 @@ int nmap_main(int argc, char *argv[]) { print_iflist(); exit(0); } + if (o.scripthelp) { + /* Special-case open_nse for --script-help only. */ + open_nse(); + exit(0); + } #if HAVE_IPV6 if(o.af() == AF_INET6 && o.traceroute) diff --git a/nse_main.cc b/nse_main.cc index f41a3a135..28fb12b44 100644 --- a/nse_main.cc +++ b/nse_main.cc @@ -6,6 +6,7 @@ #include "timing.h" #include "Target.h" #include "nmap_tty.h" +#include "xml.h" #include "nse_main.h" #include "nse_utility.h" @@ -193,6 +194,65 @@ static int scan_progress_meter (lua_State *L) return 1; } +/* This is like nmap.log_write, but doesn't append "NSE:" to the beginning of + messages. It is only used internally by nse_main.lua and is not available to + scripts. */ +static int l_log_write(lua_State *L) +{ + static const char *const ops[] = {"stdout", "stderr", NULL}; + static const int logs[] = {LOG_STDOUT, LOG_STDERR}; + int log = logs[luaL_checkoption(L, 1, NULL, ops)]; + log_write(log, "%s", luaL_checkstring(L, 2)); + return 0; +} + +static int l_xml_start_tag(lua_State *L) +{ + const char *name; + + name = luaL_checkstring(L, 1); + xml_open_start_tag(name); + + if (lua_isnoneornil(L, 2)) { + lua_newtable(L); + lua_replace(L, 2); + } + + lua_pushnil(L); + while (lua_next(L, 2)) { + xml_attribute(luaL_checkstring(L, -2), "%s", luaL_checkstring(L, -1)); + lua_pop(L, 1); + } + + xml_close_start_tag(); + + return 0; +} + +static int l_xml_end_tag(lua_State *L) +{ + xml_end_tag(); + + return 0; +} + +static int l_xml_write_escaped(lua_State *L) +{ + const char *text; + + text = luaL_checkstring(L, 1); + xml_write_escaped(text); + + return 0; +} + +static int l_xml_newline(lua_State *L) +{ + xml_newline(); + + return 0; +} + static void open_cnse (lua_State *L) { static const luaL_Reg nse[] = { @@ -208,6 +268,11 @@ static void open_cnse (lua_State *L) {"script_set_output", script_set_output}, {"host_set_output", host_set_output}, {"port_set_output", port_set_output}, + {"log_write", l_log_write}, + {"xml_start_tag", l_xml_start_tag}, + {"xml_end_tag", l_xml_end_tag}, + {"xml_write_escaped", l_xml_write_escaped}, + {"xml_newline", l_xml_newline}, {NULL, NULL} }; @@ -220,8 +285,10 @@ static void open_cnse (lua_State *L) setbfield(L, -1, "default", o.script == 1); setbfield(L, -1, "scriptversion", o.scriptversion == 1); setbfield(L, -1, "scriptupdatedb", o.scriptupdatedb == 1); + setbfield(L, -1, "scripthelp", o.scripthelp); setsfield(L, -1, "script_dbpath", SCRIPT_ENGINE_DATABASE); setsfield(L, -1, "scriptargs", o.scriptargs); + setsfield(L, -1, "NMAP_URL", NMAP_URL); } void ScriptResult::set_output (const char *out) diff --git a/nse_main.lua b/nse_main.lua index 428f0b32a..c92da6e1e 100644 --- a/nse_main.lua +++ b/nse_main.lua @@ -122,6 +122,7 @@ end local script_database_type, script_database_path = cnse.fetchfile_absolute(cnse.script_dbpath); local script_database_update = cnse.scriptupdatedb; +local script_help = cnse.scripthelp; local stdnse = require "stdnse"; @@ -170,6 +171,7 @@ end local log_write, verbosity, debugging = nmap.log_write, nmap.verbosity, nmap.debugging; +local log_write_raw = cnse.log_write; local function print_verbose (level, fmt, ...) if verbosity() >= assert(tonumber(level)) or debugging() > 0 then @@ -421,6 +423,7 @@ do portrule = portrule, postrule = postrule, args = {n = 0}; + description = rawget(env, "description"), categories = rawget(env, "categories"), author = rawget(env, "author"), license = rawget(env, "license"), @@ -819,6 +822,62 @@ local function run (threads_iter, scantype) progress "endTask"; end +-- Format NSEDoc markup (e.g., including bullet lists and sections) into +-- a display string at the given indentation level. Currently this only indents +-- the string and doesn't interpret any other markup. +local function format_nsedoc(nsedoc, indent) + indent = indent or "" + + return string.gsub(nsedoc, "([^\n]+)", indent .. "%1") +end + +-- Return the NSEDoc URL for the script with the given id. +local function nsedoc_url(id) + return string.format("%s/nsedoc/scripts/%s.html", cnse.NMAP_URL, id) +end + +local function script_help_normal(chosen_scripts) + for i, script in ipairs(chosen_scripts) do + log_write_raw("stdout", "\n"); + log_write_raw("stdout", string.format("%s\n", script.id)); + log_write_raw("stdout", string.format("Categories: %s\n", table.concat(script.categories, " "))); + log_write_raw("stdout", string.format("%s\n", nsedoc_url(script.id))); + log_write_raw("stdout", format_nsedoc(script.description, " ")); + end +end + +local function script_help_xml(chosen_scripts) + cnse.xml_start_tag("nse-scripts"); + cnse.xml_newline(); + + for i, script in ipairs(chosen_scripts) do + cnse.xml_start_tag("script", { filename = script.filename }); + cnse.xml_newline(); + + cnse.xml_start_tag("categories"); + for _, category in ipairs(script.categories) do + cnse.xml_start_tag("category"); + cnse.xml_write_escaped(category); + cnse.xml_end_tag(); + end + cnse.xml_end_tag(); + cnse.xml_newline(); + + cnse.xml_start_tag("description"); + cnse.xml_write_escaped(script.description); + cnse.xml_end_tag(); + cnse.xml_newline(); + + -- script + cnse.xml_end_tag(); + cnse.xml_newline(); + end + + -- nse-scripts + cnse.xml_end_tag(); + cnse.xml_newline(); +end + do -- Load script arguments (--script-args) local args = cnse.scriptargs or ""; @@ -919,10 +978,14 @@ end local chosen_scripts = get_chosen_scripts(rules); print_verbose(1, "Loaded %d scripts for scanning.", #chosen_scripts); for i, script in ipairs(chosen_scripts) do - -- PLEASE DO NOT CHANGE THIS FORMAT. IT IS USED BY ZENMAP TO SELECT SCRIPTS print_debug(2, "Loaded '%s'.", script.filename); end +if script_help then + script_help_normal(chosen_scripts); + script_help_xml(chosen_scripts); +end + -- main(hosts) -- This is the main function we return to NSE (on the C side), nse_main.cc -- gets this function by loading and executing nse_main.lua. This