1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 04:31:29 +00:00

Structured script output.

Scripts may now return a key–value table, or such a table in addition to
a string. The table will be automatically formatted for normal output
and will appear as a hierarchy of elements in XML output.

Some history and discussion of this development can be found at
https://secwiki.org/w/Nmap/Structured_Script_Output.

This is a merge of r29484:29569 from /nmap-exp/david/xml-output.
This commit is contained in:
david
2012-08-14 16:36:25 +00:00
parent 16aa7a938d
commit 0c3e0fcc4d
16 changed files with 680 additions and 99 deletions

View File

@@ -162,6 +162,10 @@ void Target::Recycle() {
Target::~Target() {
FreeInternal();
while (!scriptResults.empty()) {
scriptResults.front().clear();
scriptResults.pop_front();
}
}
void Target::FreeInternal() {

View File

@@ -230,12 +230,22 @@
<!ELEMENT cpe (#PCDATA)>
<!ELEMENT script EMPTY >
<!ELEMENT script (table|elem)* >
<!ATTLIST script
id CDATA #REQUIRED
output CDATA #REQUIRED
>
<!ELEMENT table (table|elem)* >
<!ATTLIST table
key CDATA #IMPLIED
>
<!ELEMENT elem (#PCDATA)>
<!ATTLIST elem
key CDATA #IMPLIED
>
<!ELEMENT os ( portused* , osmatch*, osfingerprint* ) >
<!ELEMENT portused EMPTY >

View File

@@ -38,6 +38,11 @@ Additionally, you can use:
-- | sample-script:
-- | This is some output
-- |_ Some more output
-- @xmloutput
-- <elem>This is some output</elem>
-- <table>
-- <elem>Some more output</elem>
-- </table>
--
-- @args sample-script.arg1 Here, we document each argument, how it's used, and
-- necessary, the default value.
@@ -171,23 +176,45 @@ action = function( host, port )
target.add('192.168.1.1')
end
-- If your response is more complicated, you can build a table, potentially
-- with subtables, and pass it to stdnse.format_output(). Each table can have
-- a list of output values, numerically, which will be displayed in order.
-- Additionally, they can have the 'name' key, which will be displayed at the
-- top, and the 'warning' key, which will only be displayed if debugging is
-- enabled. For more information and examples, see the documentation for
-- stdnse.format_output().
-- Construct a table representing what the script has to report.
local output_tab = stdnse.output_table()
output_tab.name1 = 'value1'
output_tab.name2 = 'value2'
output_tab.subtable = { 'sub1', 'sub2', 'sub3' }
-- Returning this table will produce output like this:
-- | sample-script:
-- | name1: value1
-- | name2: value2
-- | subtable:
-- | sub1
-- | sub2
-- |_ sub3
--
-- If you need more control over output formatting, you can return a string in
-- addition to the table. stdnse.format_output() is a formatting function used
-- to make string output. Each table can have a list of output values,
-- numerically, which will be displayed in order. Additionally, they can have
-- the 'name' key, which will be displayed at the top, and the 'warning' key,
-- which will only be displayed if debugging is enabled. For more information
-- and examples, see the documentation for stdnse.format_output().
--
-- The following will display:
-- | sample-script:
-- | value1
-- | value2
-- | Name 1: value1
-- | Name 2: value2
-- | This is a subtable
-- | subtable1
-- |_ subtable2
local response = {'value1', 'value2', {name="This is a subtable", 'subtable1', 'subtable2'}}
return stdnse.format_output(true, response)
-- | sub1
-- | sub2
-- |_ sub3
output_str = stdnse.format_output(true, {
'Name 1: ' .. 'value1',
'Name 2: ' .. 'value2',
{ name='This is a subtable', 'sub1', 'sub2', 'sub3' }
})
return output_tab, output_str
end

View File

@@ -1302,9 +1302,18 @@ NSE: Script Scanning completed.
The action is the heart of an NSE script. It contains all of the
instructions to be executed when the script's prerule, portrule, hostrule or postrule
triggers. It is a Lua function which accepts the same arguments as the
rule and can return either <literal>nil</literal> or a string. If a string is returned by a service script, the string and script's filename are printed in the Nmap port table output. A string returned by a host script is printed below the port table. No output is produced if the
script returns <literal>nil</literal>. For an example of an NSE
action refer to <xref linkend="nse-tutorial-action"/>.
rule. The return value of the action value may be a table of
name&ndash;value pairs, a string, or <code>nil</code>. For an example of
an NSE action refer to <xref linkend="nse-tutorial-action"/>.
</para>
<para>
If the output of the action is a table, it is automatically formatted in
a structured fashion for inclusion in the normal (<option>-oN</option>)
and XML (<option>-oX</option>) output formats. If a string, the text is
displayed directly in normal output, and written as an XML attribute in
XML output, No output is produced if the script returns
<literal>nil</literal>. See <xref linkend="nse-structured-output"/> for
details of how different return values are handled.
</para>
</sect2>
@@ -2166,6 +2175,140 @@ socket:close()
</sect3>
</sect2>
<sect2 id="nse-structured-output">
<title>Structured and Unstructured Output</title>
<indexterm>structured script output</indexterm>
<para>
NSE scripts should usually return a table representing their
output, one that is nicely organized and has thoughtfully chosen
keys. Such a table will be automatically formatted for screen
output and will be stored as nested elements in XML output.
Having XML output broken down logically into keys and values
makes it easier for other tools to make use of script output.
It is possible for a script to return only a string, but doing
so is deprecated. In the past, scripts could only return a
string, and their output was simply copied to the XML as a blob
of text&ndash;this is now known as <quote>unstructured
output</quote>.
</para>
<para>
Suppose a script called <filename>user-list</filename> returns a
table as shown in this code sample. The following paragraphs
show how it appears in normal and XML output.
</para>
<programlisting>
local output = stdnse.output_table()
output.hostname = "slimer"
output.users = {}
output.users[#output.users + 1] = "root"
output.users[#output.users + 1] = "foo"
output.users[#output.users + 1] = "bar"
return output
</programlisting>
<para>
A Lua table is converted to a string for normal output. The way
this works is: each nested table gets a new level of
indentation. Table entries with string keys are preceded by the
key and a colon; entries with integer keys simply appear in
order.
Unlike normal Lua tables, which are unordered, a table that
comes from <code>stdnse.output_table</code> will keep its keys in
the order they were inserted.
<xref linkend="nse-normal-structured-output"/> shows how the
example table appears in normal output.
</para>
<example id="nse-normal-structured-output">
<title>Automatic formatting of NSE structured output</title>
<screen>
PORT STATE SERVICE
1123/tcp open unknown
| user-list:
| hostname: slimer
| users:
| root
| foo
|_ bar
</screen>
</example>
<para>
The XML representation of a Lua table is constructed as follows.
Nested table become <code>table</code> elements. Entries of
tables that are not themselves tables become <code>elem</code>
elements. Entries (whether <code>table</code> or
<code>elem</code>) with string keys get a <code>key</code>
attribute (e.g.
<code>&lt;elem key="username"&gt;foo&lt;/elem&gt;</code>);
entries with integer keys have no <code>key</code> element and
their key is implicit in the order in which they appear.
</para>
<para>
In addition to the above, whatever normal output the script
produces (even if automatically generated) is copied to the
<code>output</code> attribute of the <code>script</code>
element. Newlines and other special characters will be encoded
as XML character entities, for example <code>&amp;#xa;</code>.
<xref linkend="nse-xml-structured-output"/> shows how the example
table appears in XML.
</para>
<example id="nse-xml-structured-output">
<title>NSE structured output in XML</title>
<screen><![CDATA[<script id="t" output="&#xa;hostname: slimer&#xa;users: &#xa; root&#xa; foo&#xa; bar">
<elem key="hostname">slimer</elem>
<table key="users">
<elem>root</elem>
<elem>foo</elem>
<elem>bar</elem>
</table>
</script>
]]></screen>
</example>
<para>
Some scripts need more control their normal output. This is the
case, for example, with scripts that need to display complex
tables. For complete control over the output, these scripts may
do either of these things:
<simplelist>
<member>return a string as second return value, or</member>
<member>set the <code>__tostring</code> metamethod on the
returned table.</member>
</simplelist>
The resulting string will be used in normal output, and the
table will be used in XML as usual. The formatted string may
contain newline characters to appear as multiple lines.
</para>
<para>
If the above code example were modified in this way to return a
formatted string,
<programlisting>
local output = stdnse.output_table()
output.hostname = "slimer"
output.users = {}
output.users[#output.users + 1] = "root"
output.users[#output.users + 1] = "foo"
output.users[#output.users + 1] = "bar"
local output_str = string.format("hostname: %s\n", output.hostname)
output_str = output_str .. "\n" .. stdnse.strjoin(", ", output.users)
return output, output_str
</programlisting>
then the normal output would appear as follows:
<screen>
PORT STATE SERVICE
1123/tcp open unknown
| user-list:
| hostname: slimer
|_ users: root, foo, bar
</screen>
</para>
</sect2>
<sect2 id="nse-exceptions">
<title>Exception Handling</title>
<indexterm><primary>exceptions in NSE</primary></indexterm>

10
nmap.cc
View File

@@ -1758,7 +1758,10 @@ int nmap_main(int argc, char *argv[]) {
script_scan_results = get_script_scan_results_obj();
script_scan(Targets, SCRIPT_PRE_SCAN);
printscriptresults(script_scan_results, SCRIPT_PRE_SCAN);
script_scan_results->clear();
while (!script_scan_results->empty()) {
script_scan_results->front().clear();
script_scan_results->pop_front();
}
}
#endif
@@ -2055,7 +2058,10 @@ int nmap_main(int argc, char *argv[]) {
if (o.script) {
script_scan(Targets, SCRIPT_POST_SCAN);
printscriptresults(script_scan_results, SCRIPT_POST_SCAN);
script_scan_results->clear();
while (!script_scan_results->empty()) {
script_scan_results->front().clear();
script_scan_results->pop_front();
}
delete new_targets;
new_targets = NULL;
}

View File

@@ -36,6 +36,9 @@
#define NSE_SELECTED_BY_NAME "NSE_SELECTED_BY_NAME"
#define NSE_CURRENT_HOSTS "NSE_CURRENT_HOSTS"
#define NSE_FORMAT_TABLE "NSE_FORMAT_TABLE"
#define NSE_FORMAT_XML "NSE_FORMAT_XML"
#ifndef MAXPATHLEN
# define MAXPATHLEN 2048
#endif
@@ -113,7 +116,9 @@ static int script_set_output (lua_State *L)
{
ScriptResult sr;
sr.set_id(luaL_checkstring(L, 1));
sr.set_output(luaL_checkstring(L, 2));
sr.set_output_tab(L, 2);
if (!lua_isnil(L, 3))
sr.set_output_str(luaL_checkstring(L, 3));
script_scan_results.push_back(sr);
return 0;
}
@@ -123,7 +128,9 @@ static int host_set_output (lua_State *L)
ScriptResult sr;
Target *target = nseU_gettarget(L, 1);
sr.set_id(luaL_checkstring(L, 2));
sr.set_output(luaL_checkstring(L, 3));
sr.set_output_tab(L, 3);
if (!lua_isnil(L, 4))
sr.set_output_str(luaL_checkstring(L, 4));
target->scriptResults.push_back(sr);
return 0;
}
@@ -136,7 +143,9 @@ static int port_set_output (lua_State *L)
Target *target = nseU_gettarget(L, 1);
p = nseU_getport(L, target, &port, 2);
sr.set_id(luaL_checkstring(L, 3));
sr.set_output(luaL_checkstring(L, 4));
sr.set_output_tab(L, 4);
if (!lua_isnil(L, 5))
sr.set_output_str(luaL_checkstring(L, 5));
target->ports.addScriptResult(p->portno, p->proto, sr);
target->ports.numscriptresults++;
return 0;
@@ -236,6 +245,19 @@ static int l_xml_newline(lua_State *L)
return 0;
}
static int l_protect_xml(lua_State *L)
{
const char *text;
size_t len;
std::string output;
text = luaL_checklstring(L, 1, &len);
output = protect_xml(std::string(text, len));
lua_pushlstring(L, output.c_str(), output.size());
return 1;
}
static int nse_fetch (lua_State *L, int (*fetch)(char *, size_t, const char *))
{
char path[MAXPATHLEN];
@@ -340,6 +362,7 @@ static void open_cnse (lua_State *L)
{"xml_end_tag", l_xml_end_tag},
{"xml_write_escaped", l_xml_write_escaped},
{"xml_newline", l_xml_newline},
{"protect_xml", l_protect_xml},
{NULL, NULL}
};
@@ -356,14 +379,76 @@ static void open_cnse (lua_State *L)
}
void ScriptResult::set_output (const char *out)
/* Global persistent Lua state used by the engine. */
static lua_State *L_NSE = NULL;
void ScriptResult::clear (void)
{
output = std::string(out);
if (o.debugging > 3)
log_write(LOG_STDOUT, "ScriptResult::clear %d id %s\n", output_ref, get_id());
luaL_unref(L_NSE, LUA_REGISTRYINDEX, output_ref);
output_ref = LUA_NOREF;
}
const char *ScriptResult::get_output (void) const
void ScriptResult::set_output_tab (lua_State *L, int pos)
{
return output.c_str();
clear();
lua_pushvalue(L, pos);
output_ref = luaL_ref(L_NSE, LUA_REGISTRYINDEX);
if (o.debugging > 3)
log_write(LOG_STDOUT, "ScriptResult::set_output_tab %d id %s\n", output_ref, get_id());
}
void ScriptResult::set_output_str (const char *out)
{
output_str = std::string(out);
}
static std::string format_obj(lua_State *L, int pos)
{
std::string output;
pos = lua_absindex(L, pos);
/* Look up the FORMAT_TABLE function from nse_main.lua and call it. */
lua_getfield(L, LUA_REGISTRYINDEX, NSE_FORMAT_TABLE);
if (lua_isnil(L, -1)) {
log_write(LOG_STDOUT, "%s: Cannot find function _R[\"%s\"] that should be in nse_main.lua\n",
SCRIPT_ENGINE, NSE_FORMAT_TABLE);
lua_pop(L, 1);
return output;
}
lua_pushvalue(L, pos);
if (lua_pcall(L, 1, 1, 0) != 0) {
if (o.debugging)
log_write(LOG_STDOUT, "%s: Error in FORMAT_TABLE: %s\n", SCRIPT_ENGINE, lua_tostring(L, -1));
lua_pop(L, 1);
return output;
}
output = std::string(lua_tostring(L, -1));
lua_pop(L, 1);
return output;
}
std::string ScriptResult::get_output_str (void) const
{
std::string output;
/* Explicit string output? */
if (!output_str.empty())
return output_str;
/* Auto-formatted table output? */
lua_rawgeti(L_NSE, LUA_REGISTRYINDEX, output_ref);
if (!lua_isnil(L_NSE, -1))
output = format_obj(L_NSE, -1);
lua_pop(L_NSE, 1);
return output;
}
void ScriptResult::set_id (const char *ident)
@@ -381,12 +466,50 @@ ScriptResults *get_script_scan_results_obj (void)
return &script_scan_results;
}
static void format_xml(lua_State *L, int pos)
{
pos = lua_absindex(L, pos);
/* Look up the FORMAT_XML function from nse_main.lua and call it. */
lua_getfield(L, LUA_REGISTRYINDEX, NSE_FORMAT_XML);
if (lua_isnil(L, -1)) {
log_write(LOG_STDOUT, "%s: Cannot find function _R[\"%s\"] that should be in nse_main.lua\n",
SCRIPT_ENGINE, NSE_FORMAT_XML);
lua_pop(L, 1);
return;
}
lua_pushvalue(L, pos);
if (lua_pcall(L, 1, 1, 0) != 0) {
if (o.debugging)
log_write(LOG_STDOUT, "%s: Error in FORMAT_XML: %s\n", SCRIPT_ENGINE, lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
}
void ScriptResult::write_xml() const
{
std::string output_str;
xml_open_start_tag("script");
xml_attribute("id", "%s", get_id());
xml_attribute("output", "%s", get_output());
xml_close_empty_tag();
output_str = get_output_str();
if (!output_str.empty())
xml_attribute("output", "%s", protect_xml(output_str).c_str());
/* Any table output? */
lua_rawgeti(L_NSE, LUA_REGISTRYINDEX, output_ref);
if (!lua_isnil(L_NSE, -1)) {
xml_close_start_tag();
format_xml(L_NSE, -1);
xml_end_tag();
} else {
xml_close_empty_tag();
}
lua_pop(L_NSE, 1);
}
/* int panic (lua_State *L)
@@ -625,9 +748,6 @@ void nse_gettarget (lua_State *L, int index)
lua_replace(L, -2);
}
/* Global persistent Lua state used by the engine. */
static lua_State *L_NSE = NULL;
void open_nse (void)
{
if (L_NSE == NULL)

View File

@@ -19,11 +19,20 @@ extern "C" {
class ScriptResult
{
private:
std::string output;
std::string id;
/* Structured output table, an integer ref in L_NSE[LUA_REGISTRYINDEX]. */
int output_ref;
/* Unstructured output string, for scripts that do not return a structured
table, or return a string in addition to a table. */
std::string output_str;
public:
void set_output (const char *);
const char *get_output (void) const;
ScriptResult() {
output_ref = LUA_NOREF;
}
void clear (void);
void set_output_tab (lua_State *, int);
void set_output_str (const char *);
std::string get_output_str (void) const;
void set_id (const char *);
const char *get_id (void) const;
void write_xml() const;

View File

@@ -43,6 +43,8 @@ local BASE = "NSE_BASE";
local WAITING_TO_RUNNING = "NSE_WAITING_TO_RUNNING";
local DESTRUCTOR = "NSE_DESTRUCTOR";
local SELECTED_BY_NAME = "NSE_SELECTED_BY_NAME";
local FORMAT_TABLE = "NSE_FORMAT_TABLE";
local FORMAT_XML = "NSE_FORMAT_XML";
-- This is a limit on the number of script instance threads running at once. It
-- exists only to limit memory use when there are many open ports. It doesn't
@@ -283,14 +285,25 @@ do
print_debug(1, fmt, ...);
end
-- Sets scripts output. Variable result is a string.
function Thread:set_output(result)
-- Sets script output. r1 and r2 are the (as many as two) return values.
function Thread:set_output(r1, r2)
-- Structure table and unstructured string outputs.
local tab, str
if r2 then
tab, str = r1, r2;
elseif type(r1) == "string" then
tab, str = nil, r1;
else
tab, str = r1, nil;
end
if self.type == "prerule" or self.type == "postrule" then
cnse.script_set_output(self.id, result);
cnse.script_set_output(self.id, tab, str);
elseif self.type == "hostrule" then
cnse.host_set_output(self.host, self.id, result);
cnse.host_set_output(self.host, self.id, tab, str);
elseif self.type == "portrule" then
cnse.port_set_output(self.host, self.port, self.id, result);
cnse.port_set_output(self.host, self.port, self.id, tab, str);
end
end
@@ -902,18 +915,19 @@ local function run (threads_iter, hosts)
current, running[co] = thread, nil;
thread:start_time_out_clock();
local s, result = resume(co, unpack(thread.args, 1, thread.args.n));
-- Threads may have zero, one, or two return values.
local s, r1, r2 = resume(co, unpack(thread.args, 1, thread.args.n));
if not s then -- script error...
all[co], num_threads = nil, num_threads-1;
if debugging() > 0 then
thread:d("%THREAD_AGAINST threw an error!\n%s\n",
traceback(co, tostring(result)));
traceback(co, tostring(r1)));
else
thread:set_output("ERROR: Script execution failed (use -d to debug)");
end
thread:close(timeouts, result);
thread:close(timeouts, r1);
elseif status(co) == "suspended" then
if result == NSE_YIELD_VALUE then
if r1 == NSE_YIELD_VALUE then
waiting[co] = thread;
else
all[co], num_threads = nil, num_threads-1;
@@ -922,15 +936,7 @@ local function run (threads_iter, hosts)
end
elseif status(co) == "dead" then
all[co], num_threads = nil, num_threads-1;
if type(result) == "string" then
-- Escape any character outside the range 32-126 except for tab,
-- carriage return, and line feed. This makes the string safe for
-- screen display as well as XML (see section 2.2 of the XML spec).
result = gsub(result, "[^\t\r\n\032-\126]", function(a)
return format("\\x%02X", byte(a));
end);
thread:set_output(result);
end
thread:set_output(r1, r2);
thread:d("Finished %THREAD_AGAINST.");
thread:close(timeouts);
end
@@ -949,6 +955,75 @@ local function run (threads_iter, hosts)
progress "endTask";
end
-- This function does the automatic formatting of Lua objects into strings, for
-- normal output and for the XML @output attribute. Each nested table is
-- indented by two spaces. Tables having a __tostring metamethod are converted
-- using tostring. Otherwise, integer keys are listed first and only their
-- value is shown; then string keys are shown prefixed by the key and a colon.
-- Any other kinds of keys. Anything that is not a table is converted to a
-- string with tostring.
local function format_table(obj, indent)
indent = indent or " ";
if type(obj) == "table" then
local mt = getmetatable(obj)
if mt and mt["__tostring"] then
-- Table obeys tostring, so use that.
return tostring(obj)
end
local lines = {};
-- Do integer keys.
for _, v in ipairs(obj) do
lines[#lines + 1] = indent .. format_table(v, indent .. " ");
end
-- Do string keys.
for k, v in pairs(obj) do
if type(k) == "string" then
lines[#lines + 1] = indent .. k .. ": " .. format_table(v, indent .. " ");
end
end
return "\n" .. concat(lines, "\n");
else
return tostring(obj);
end
end
_R[FORMAT_TABLE] = format_table
local format_xml
local function format_xml_elem(obj, key)
if key then
key = cnse.protect_xml(tostring(key));
end
if type(obj) == "table" then
cnse.xml_start_tag("table", {key=key});
cnse.xml_newline();
else
cnse.xml_start_tag("elem", {key=key});
end
format_xml(obj);
cnse.xml_end_tag();
cnse.xml_newline();
end
-- This function writes an XML representation of a Lua object to the XML stream.
function format_xml(obj, key)
if type(obj) == "table" then
-- Do integer keys.
for _, v in ipairs(obj) do
format_xml_elem(v);
end
-- Do string keys.
for k, v in pairs(obj) do
if type(k) == "string" then
format_xml_elem(v, k);
end
end
else
cnse.xml_write_escaped(cnse.protect_xml(tostring(obj)));
end
end
_R[FORMAT_XML] = format_xml
-- Format NSEDoc markup (e.g., including bullet lists and <code> sections) into
-- a display string at the given indentation level. Currently this only indents
-- the string and doesn't interpret any other markup.

View File

@@ -7,6 +7,7 @@
-- @name stdnse
local _G = require "_G"
local coroutine = require "coroutine"
local math = require "math"
local nmap = require "nmap"
local os = require "os"
@@ -17,6 +18,7 @@ local error = error;
local getmetatable = getmetatable;
local ipairs = ipairs
local pairs = pairs
local rawset = rawset
local require = require;
local select = select
local setmetatable = setmetatable;
@@ -1032,4 +1034,50 @@ function seeall (env)
setmetatable(env, m);
end
--- Return a table that keeps elements in order of insertion.
--
-- The pairs function, called on a table returned by this function, will yield
-- elements in the order they were inserted. This function is meant to be used
-- to construct output tables returned by scripts.
--
-- Reinserting a key that is already in the table does not change its position
-- in the order. However, removing a key by assigning to <code>nil</code> and
-- then doing another assignment will move the key to the end of the order.
--
-- @return An ordered table.
function output_table ()
local t = {}
local reverse = {}
local order = {}
local function iterator ()
for i, key in ipairs(order) do
coroutine.yield(key, t[key])
end
end
local mt = {
__newindex = function (_, k, v)
if reverse[k] then
rawset(t, k, v)
if v == nil then
table.remove(order, reverse[k])
reverse[k] = nil
end
else
if v ~= nil then
table.insert(order, k)
reverse[k] = #order
end
rawset(t, k, v)
end
end,
__index = function (_, k)
return t[k]
end,
__pairs = function (_)
return coroutine.wrap(iterator)
end,
}
return setmetatable({}, mt)
end
return _ENV;

View File

@@ -425,16 +425,46 @@ int print_iflist(void) {
}
#ifndef NOLUA
/* Escape control characters to make a string safe to display on a terminal. */
static std::string escape_for_screen(const std::string s) {
std::string r;
for (unsigned int i = 0; i < s.size(); i++) {
char buf[5];
unsigned char c = s[i];
if (c == '\t' || c == '\r' || c == '\n' || (0x20 <= c && c <= 0x7e)) {
r += c;
} else {
Snprintf(buf, sizeof(buf), "\\x%02X", c);
r += buf;
}
}
return r;
}
/* Do something to protect characters that can't appear in XML. This is not a
reversible transform, more a last-ditch effort to write readable XML with
characters that shouldn't be part of regular output anyway. The escaping that
xml_write_escaped is not enough; some characters are not allowed to appear in
XML, not even escaped. */
std::string protect_xml(const std::string s) {
/* escape_for_screen is good enough. */
return escape_for_screen(s);
}
static char *formatScriptOutput(ScriptResult sr) {
std::vector<std::string> lines;
const char *c_output;
std::string c_output;
const char *p, *q;
std::string result;
unsigned int i;
c_output = sr.get_output();
p = c_output;
c_output = escape_for_screen(sr.get_output_str());
if (c_output.empty())
return NULL;
p = c_output.c_str();
while (*p != '\0') {
q = strchr(p, '\n');
@@ -798,8 +828,10 @@ void printportoutput(Target *currenths, PortList *plist) {
ssr_iter->write_xml();
char *script_output = formatScriptOutput((*ssr_iter));
Tbl->addItem(rowno, 0, true, true, script_output);
free(script_output);
if (script_output != NULL) {
Tbl->addItem(rowno, 0, true, true, script_output);
free(script_output);
}
rowno++;
}
@@ -2198,8 +2230,10 @@ void printscriptresults(ScriptResults *scriptResults, stype scantype) {
iter->write_xml();
script_output = formatScriptOutput((*iter));
log_write(LOG_PLAIN, "%s\n", script_output);
free(script_output);
if (script_output != NULL) {
log_write(LOG_PLAIN, "%s\n", script_output);
free(script_output);
}
}
xml_end_tag();
}
@@ -2220,8 +2254,10 @@ void printhostscriptresults(Target *currenths) {
iter->write_xml();
script_output = formatScriptOutput((*iter));
log_write(LOG_PLAIN, "%s\n", script_output);
free(script_output);
if (script_output != NULL) {
log_write(LOG_PLAIN, "%s\n", script_output);
free(script_output);
}
}
xml_end_tag();
}

View File

@@ -220,6 +220,8 @@ void printosscanoutput(Target *currenths);
void printserviceinfooutput(Target *currenths);
#ifndef NOLUA
std::string protect_xml(const std::string s);
/* Use this function to report NSE_PRE_SCAN and NSE_POST_SCAN results */
void printscriptresults(ScriptResults *scriptResults, stype scantype);

View File

@@ -147,6 +147,14 @@ void Port::freeService(bool del_service) {
}
}
void Port::freeScriptResults(void)
{
while (!scriptResults.empty()) {
scriptResults.front().clear();
scriptResults.pop_front();
}
}
/* Fills in namebuf (as long as there is space in buflen) with the
Name nmap normal output will use to describe the port. This takes
into account to confidence level, any SSL tunneling, etc. Truncates
@@ -508,6 +516,9 @@ PortList::~PortList() {
for(i=0; i < port_list_count[proto]; i++) { // free every Port
if(port_list[proto][i]) {
port_list[proto][i]->freeService(true);
#ifndef NOLUA
port_list[proto][i]->freeScriptResults();
#endif
delete port_list[proto][i];
}
}

View File

@@ -181,6 +181,7 @@ class Port {
public:
Port();
void freeService(bool del_service);
void freeScriptResults(void);
void getNmapServiceName(char *namebuf, int buflen, const char *rpcinfo) const;
u16 portno;

View File

@@ -13,9 +13,13 @@ sent, so the difference includes at least the duration of one RTT.
---
-- @output
-- 80/tcp open http
-- |_ http-date: Thu, 23 Jul 2009 23:15:57 GMT; -6s from local time.
-- |_http-date: Thu, 02 Aug 2012 22:11:03 GMT; 0s from local time.
-- 80/tcp open http
-- |_ http-date: Wed, 17 Jan 2007 09:29:10 GMT; -2y187d13h46m53s from local time.
-- |_http-date: Thu, 02 Aug 2012 22:07:12 GMT; -3m51s from local time.
--
-- @xmloutput
-- <elem key="date">2012-08-02T23:07:12Z</elem>
-- <elem key="delta">-231</elem>
author = "David Fifield"
@@ -39,9 +43,13 @@ action = function(host, port)
return
end
-- Should account for estimated RTT too.
local diff = stdnse.format_difftime(response_date, request_date)
local output_tab = stdnse.output_table()
-- ISO 8601 date and time.
output_tab.date = os.date("%Y-%m-%dT%H:%M:%SZ", os.time(response_date))
output_tab.delta = os.difftime(os.time(response_date), os.time(request_date))
return string.format("%s; %s from local time.",
response.header["date"], diff)
local output_str = string.format("%s; %s from local time.",
response.header["date"], stdnse.format_difftime(response_date, request_date))
return output_tab, output_str
end

View File

@@ -22,6 +22,12 @@ original target.
-- PORT STATE SERVICE
-- 80/tcp open http
-- |_http-title: Go ahead and ScanMe!
--
-- @xmloutput
-- <elem key="title">Go ahead and ScanMe!</elem>
-- @xmloutput
-- <elem key="title">Wikipedia, the free encyclopedia</elem>
-- <elem key="redirect_url">http://en.wikipedia.org/wiki/Main_Page</elem>
author = "Diman Todorov"
@@ -41,7 +47,7 @@ action = function(host, port)
if resp.location then
redirect_url = resp.location[#resp.location]
if resp.status and tostring( resp.status ):match( "30%d" ) then
return ("Did not follow redirect to %s"):format( redirect_url )
return {redirect_url = redirect_url}, ("Did not follow redirect to %s"):format( redirect_url )
end
end
@@ -64,10 +70,14 @@ action = function(host, port)
end
end
local output_tab = stdnse.output_table()
output_tab.title = title
output_tab.redirect_url = redirect_url
local output_str = display_title
if redirect_url then
output_str = output_str .. "\n" .. ("Requested resource was %s"):format( redirect_url )
end
return output_str
return output_tab, output_str
end

View File

@@ -13,24 +13,26 @@ organizationName, stateOrProvinceName, and countryName of the subject.
<code>
443/tcp open https
| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
/stateOrProvinceName=California/countryName=US
| Not valid before: 2009-05-28 00:00:00
|_ Not valid after: 2010-05-01 23:59:59
| Not valid before: 2011-03-23 00:00:00
|_Not valid after: 2013-04-01 23:59:59
</code>
With <code>-v</code> it adds the issuer name and fingerprints.
<code>
443/tcp open https
| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
/stateOrProvinceName=California/countryName=US
| Issuer: commonName=VeriSign Class 3 Extended Validation SSL CA\
| Issuer: commonName=VeriSign Class 3 Extended Validation SSL CA\
/organizationName=VeriSign, Inc./countryName=US
| Not valid before: 2009-05-28 00:00:00
| Not valid after: 2010-05-01 23:59:59
| MD5: c5b8 7ddd ccc7 537f 8861 b476 078d e8fd
|_ SHA-1: dc5a cb8b 9eb9 b5de 7117 c536 8c15 0e75 ba88 702e
| Public Key type: rsa
| Public Key bits: 2048
| Not valid before: 2011-03-23 00:00:00
| Not valid after: 2013-04-01 23:59:59
| MD5: bf47 ceca d861 efa7 7d14 88ad 4a73 cb5b
|_SHA-1: d846 5221 467a 0d15 3df0 9f2e af6d 4390 0213 9a68
</code>
With <code>-vv</code> it adds the PEM-encoded contents of the entire
@@ -38,32 +40,73 @@ certificate.
<code>
443/tcp open https
| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
/stateOrProvinceName=California/countryName=US/serialNumber=3014267\
/1.3.6.1.4.1.311.60.2.1.3=US/streetAddress=2211 N 1st St\
/1.3.6.1.4.1.311.60.2.1.2=Delaware/postalCode=95131-2021\
/localityName=San Jose/organizationalUnitName=Information Systems\
/2.5.4.15=V1.0, Clause 5.(b)
| Issuer: commonName=VeriSign Class 3 Extended Validation SSL CA\
| ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
/stateOrProvinceName=California/countryName=US/1.3.6.1.4.1.311.60.2.1.2=Delaware\
/postalCode=95131-2021/localityName=San Jose/serialNumber=3014267\
/streetAddress=2211 N 1st St/1.3.6.1.4.1.311.60.2.1.3=US\
/organizationalUnitName=PayPal Production/businessCategory=Private Organization
| Issuer: commonName=VeriSign Class 3 Extended Validation SSL CA\
/organizationName=VeriSign, Inc./countryName=US\
/organizationalUnitName=Terms of use at https://www.verisign.com/rpa (c)06
| Not valid before: 2009-05-28 00:00:00
| Not valid after: 2010-05-01 23:59:59
| MD5: c5b8 7ddd ccc7 537f 8861 b476 078d e8fd
| SHA-1: dc5a cb8b 9eb9 b5de 7117 c536 8c15 0e75 ba88 702e
| -----BEGIN CERTIFICATE-----
| MIIFxzCCBK+gAwIBAgIQX02QuADDB7CVjZdooVge+zANBgkqhkiG9w0BAQUFADCB
| Public Key type: rsa
| Public Key bits: 2048
| Not valid before: 2011-03-23 00:00:00
| Not valid after: 2013-04-01 23:59:59
| MD5: bf47 ceca d861 efa7 7d14 88ad 4a73 cb5b
| SHA-1: d846 5221 467a 0d15 3df0 9f2e af6d 4390 0213 9a68
| -----BEGIN CERTIFICATE-----
| MIIGSzCCBTOgAwIBAgIQLjOHT2/i1B7T//819qTJGDANBgkqhkiG9w0BAQUFADCB
...
| 9YDR12XLZeQjO1uiunCsJkDIf9/5Mqpu57pw8v1QNA==
|_-----END CERTIFICATE-----
</code>
]]
---
-- @output
-- 443/tcp open https
-- | ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
-- | ssl-cert: Subject: commonName=www.paypal.com/organizationName=PayPal, Inc.\
-- /stateOrProvinceName=California/countryName=US
-- | Not valid before: 2009-05-28 00:00:00
-- |_ Not valid after: 2010-05-01 23:59:59
-- | Not valid before: 2011-03-23 00:00:00
-- |_Not valid after: 2013-04-01 23:59:59
--
-- @xmloutput
-- <table key="subject">
-- <elem key="1.3.6.1.4.1.311.60.2.1.2">Delaware</elem>
-- <elem key="1.3.6.1.4.1.311.60.2.1.3">US</elem>
-- <elem key="postalCode">95131-2021</elem>
-- <elem key="localityName">San Jose</elem>
-- <elem key="serialNumber">3014267</elem>
-- <elem key="countryName">US</elem>
-- <elem key="stateOrProvinceName">California</elem>
-- <elem key="streetAddress">2211 N 1st St</elem>
-- <elem key="organizationalUnitName">PayPal Production</elem>
-- <elem key="commonName">www.paypal.com</elem>
-- <elem key="organizationName">PayPal, Inc.</elem>
-- <elem key="businessCategory">Private Organization</elem>
-- </table>
-- <table key="issuer">
-- <elem key="organizationalUnitName">Terms of use at https://www.verisign.com/rpa (c)06</elem>
-- <elem key="organizationName">VeriSign, Inc.</elem>
-- <elem key="commonName">VeriSign Class 3 Extended Validation SSL CA</elem>
-- <elem key="countryName">US</elem>
-- </table>
-- <table key="pubkey">
-- <elem key="type">rsa</elem>
-- <elem key="bits">2048</elem>
-- </table>
-- <table key="validity">
-- <elem key="notBefore">2011-03-23T00:00:00Z</elem>
-- <elem key="notAfter">2013-04-01T23:59:59Z</elem>
-- </table>
-- <elem key="md5">bf47cecad861efa77d1488ad4a73cb5b</elem>
-- <elem key="sha1">d8465221467a0d153df09f2eaf6d439002139a68</elem>
-- <elem key="pem">-----BEGIN CERTIFICATE-----
-- MIIGSzCCBTOgAwIBAgIQLjOHT2/i1B7T//819qTJGDANBgkqhkiG9w0BAQUFADCB
-- ...
-- 9YDR12XLZeQjO1uiunCsJkDIf9/5Mqpu57pw8v1QNA==
-- -----END CERTIFICATE-----
-- </elem>
author = "David Fifield"
@@ -129,7 +172,37 @@ function stringify_name(name)
return stdnse.strjoin("/", fields)
end
local function parseCertificate(cert)
local function name_to_table(name)
local output = {}
for k, v in pairs(name) do
if type(k) == "table" then
k = stdnse.strjoin(".", k)
end
output[k] = v
end
return output
end
local function format_time(t)
return os.date("%Y-%m-%dT%H:%M:%SZ", os.time(t))
end
local function output_tab(cert)
local o = stdnse.output_table()
o.subject = name_to_table(cert.subject)
o.issuer = name_to_table(cert.issuer)
o.pubkey = cert.pubkey
o.validity = {}
for k, v in pairs(cert.validity) do
o.validity[k] = format_time(v)
end
o.md5 = stdnse.tohex(cert:digest("md5"))
o.sha1 = stdnse.tohex(cert:digest("sha1"))
o.pem = cert.pem
return o
end
local function output_str(cert)
local lines = {}
lines[#lines + 1] = "Subject: " .. stringify_name(cert.subject)
@@ -156,7 +229,7 @@ local function parseCertificate(cert)
if nmap.verbosity() > 1 then
lines[#lines + 1] = cert.pem
end
return lines
return stdnse.strjoin("\n", lines)
end
action = function(host, port)
@@ -164,10 +237,8 @@ action = function(host, port)
if ( not(status) ) then
return
end
local lines = parseCertificate(cert)
return stdnse.strjoin("\n", lines)
return output_tab(cert), output_str(cert)
end