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

o Added a new NSE Comm library for common network discovery tasks such

as banner-grabbing (get_banner()) and making a quick exchange of data
  (exchange()).  16 scripts were updated to use this library. [Kris]

I have *not* been able to test all of these scripts; however, I have
reviewed them and they should all work properly.  I would really like
some more testing, though :)

This commit includes scripting.xml documentation.
This commit is contained in:
kris
2008-06-12 14:32:25 +00:00
parent cede9ab542
commit 2f9321360f
19 changed files with 297 additions and 268 deletions

View File

@@ -28,6 +28,10 @@ o Fixed the nmap_command_path bug in Zenmap. The variable now actually
by a new class in UmitConf called PathsConfig, which represents the
[paths] section in zenmap.conf. [Jurand Nogiec]
o Added a new NSE Comm library for common network discovery tasks such
as banner-grabbing (get_banner()) and making a quick exchange of data
(exchange()). 16 scripts were updated to use this library. [Kris]
o Fixed a bug which caused -PN to erronously bail out for unprivileged
users. Thanks to Jabra (jabra(a)spl0it.org) for the report. [Kris]

View File

@@ -1441,6 +1441,61 @@ if(s) code_to_be_done_on_match end
</varlistentry>
</variablelist>
</sect2>
<sect2 id="nse-lib-comm">
<title>Common Communication Functions</title>
<para>
The <literal>comm</literal> module provides functions for common network discovery
tasks such as banner-grabbing and making a quick exchange of data. These functions'
return values are setup for use with exception handling via <literal>nmap.new_try()</literal>.
</para>
<para>
These functions can all be passed a table of options, but it's not required.
The relevant indexes for this table are <literal>bytes</literal>, <literal>lines</literal>,
<literal>proto</literal> and <literal>timeout</literal>. <literal>bytes</literal>
is used to provide the minimum number of bytes required for a read. <literal>lines</literal>
does the same, but for the minimum number of lines. <literal>proto</literal> is used
to set the protocol to communicate with, defaulting to "tcp" if not provided.
<literal>timeout</literal> is used to set the socket timeout (see the socket function
<literal>set_timeout()</literal> for details).
</para>
<variablelist>
<varlistentry>
<term><option>bool, response = comm.get_banner(host, port, [options])</option>
<indexterm><primary>get_banner</primary></indexterm></term>
<listitem>
<para>
This function simply connects to the specified port number on
the specified host and returns any data received.
<literal>bool</literal> is a boolean value indicating success.
If <literal>bool</literal> is true, then the second returned
value is the response from the target host. If <literal>bool</literal>
is false, an error message is returned as the second value instead
of a response.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>bool, response = comm.exchange(host, port, data, [options])</option>
<indexterm><primary>exchange</primary></indexterm></term>
<listitem>
<para>
This function connects to the specified port number on the
specified host, sends <literal>data</literal>, then waits for
and returns the response, if any. <literal>bool</literal> is a
boolean value indicating success. If <literal>bool</literal> is
true, then the second returned value is the response from the
target host. If <literal>bool</literal> is false, an error message
is returned as the second value instead of a response.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
<sect2 id="nse-lib-datafiles">
<title>Data File Parsing Functions</title>
<para>

151
nselib/comm.lua Normal file
View File

@@ -0,0 +1,151 @@
-- Kris Katterjohn 04/2008
module(..., package.seeall)
------
--
-- The Functions:
--
-- get_banner(host, port, [opts])
-- exchange(host, port, data, [opts])
--
-- get_banner() does just what it sounds like it does: connects to the
-- host, reads whatever it gives us, and then returns it.
--
-- exchange() connects to the host, sends the requested data, reads
-- whatever it gives us, and then returns it.
--
-- Both of these functions return multiple values so that they can be
-- used with exception handling via nmap.new_try(). The second value
-- they return is either the response from the host, or the error message
-- from one of the previous calls (connect, send, receive*).
--
-- These functions can be passed a table of options with the following keys:
--
-- bytes: Specifies the minimum amount of bytes are to be read from the host
-- lines: Specifies the minimum amount of lines are to be read from the host
-- proto: Specifies the protocol to be used with the connect() call
-- timeout: Sets the socket's timeout with nmap.set_timeout()
--
-- If neither lines nor bytes are specified, the calls read as many lines
-- as possible. If only bytes if specified, then it only tries to read that
-- many bytes. Likewise, it only lines if specified, then it only tries to
-- read that many lines. If they're both specified, the lines value is used.
--
------
-- Makes sure that opts exists and the default proto is there
local initopts = function(opts)
if not opts then
opts = {}
end
if not opts.proto then
opts.proto = "tcp"
end
return opts
end
-- Sets up the socket and connects to host:port
local setup_connect = function(host, port, opts)
if type(host) ~= "table" then
host = {ip = host}
end
local target = host.targetname or host.ip or host.name
if type(port) ~= "table" then
port = {number = port}
end
local sock = nmap.new_socket()
if opts.timeout then
sock:set_timeout(opts.timeout)
end
local status, err = sock:connect(target, port.number, opts.proto)
if not status then
return status, err
end
return true, sock
end
local read = function(sock, opts)
local line, response, status
if opts.lines then
status, response = sock:receive_lines(opts.lines)
return status, response
elseif opts.bytes then
status, response = sock:receive_bytes(opts.bytes)
return status, response
end
response = ""
while true do
status, line = sock:receive_lines(1)
if not status then
break
end
response = response .. line
end
-- Either we reached the end of the stream, or we got all we could
-- within the socket timeout
if line == "EOF" or (line == "TIMEOUT" and response ~= "") then
return true, response
end
return false, line
end
get_banner = function(host, port, opts)
opts = initopts(opts)
local status, sock = setup_connect(host, port, opts)
local ret
if not status then
-- sock is an error message in this case
return status, sock
end
status, ret = read(sock, opts)
sock:close()
return status, ret
end
exchange = function(host, port, data, opts)
opts = initopts(opts)
local status, sock = setup_connect(host, port, opts)
local ret
if not status then
-- sock is an error message in this case
return status, sock
end
status, ret = sock:send(data)
if not status then
sock:close()
return status, ret
end
status, ret = read(sock, opts)
sock:close()
return status, ret
end

View File

@@ -8,6 +8,7 @@
id="Open Proxy Test"
description="Test if a discovered proxy is open to us by connecting to www.google.com and checking for the 'Server: GWS/' header response."
categories = {"default", "intrusive"}
require "comm"
-- I found a nice explode() function in lua-users' wiki. I had to fix it, though.
-- http://lua-users.org/wiki/LuaRecipes
@@ -39,28 +40,21 @@ portrule = function(host, port)
end
action = function(host, port)
local socket = nmap.new_socket()
local result
local status = true
local response
local i
-- We will return this if we don't find "^Server: GWS" in response headers
local retval
socket:set_timeout(10000);
socket:connect(host.ip, port.number, port.protocol)
-- Ask proxy to open www.google.com
socket:send("GET http://www.google.com HTTP/1.0\r\nHost: www.google.com\r\n\r\n")
local req = "GET http://www.google.com HTTP/1.0\r\nHost: www.google.com\r\n\r\n"
local status, result = comm.exchange(host, port, req, {proto=port.protocol, timeout=10000})
-- read the response, if any
status, result = socket:receive_lines(1)
if not status then
return
end
-- Explode result into the response table
if (status == false) or (result == "TIMEOUT") then
else
response = explode("\n",result)
end
-- Now, search for Server: GWS until headers (or table) end.
i = 0
@@ -74,7 +68,5 @@ action = function(host, port)
end
end
-- close the socket and exit, returning the retval string.
socket:close()
return retval
end

View File

@@ -18,6 +18,7 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery"}
require "comm"
require "shortport"
require "stdnse"
@@ -76,31 +77,14 @@ end
portrule = shortport.port_or_service({80, 8080}, "http")
action = function(host, port)
local cmd, response
local socket
local cmd = "TRACE / HTTP/1.0\r\n\r\n"
socket = nmap.new_socket()
socket:connect(host.ip, port.number)
cmd = "TRACE / HTTP/1.0\r\n\r\n"
socket:send(cmd)
response = ""
while true do
local status, lines = socket:receive_lines(1)
local status, response = comm.exchange(host, port, cmd, {timeout=5000})
if not status then
break
return
end
response = response .. lines
end
socket:close()
return validate(response, cmd)
end

View File

@@ -18,6 +18,7 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = { "default", "discovery", "safe" }
require 'bit'
require 'comm'
-- Grabs NUL-terminated string
local getstring = function(orig)
@@ -105,28 +106,14 @@ portrule = function(host, port)
end
action = function(host, port)
local sock
local response = ""
local output = ""
sock = nmap.new_socket()
sock:set_timeout(5000)
sock:connect(host.ip, port.number)
while true do
local status, line = sock:receive_lines(1)
local status, response = comm.get_banner(host, port, {timeout=5000})
if not status then
break
return
end
response = response .. line
end
sock:close()
local length = ntoh3(response:sub(1, 3))
if length ~= response:len() - 4 then

View File

@@ -11,6 +11,8 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"version"}
require "comm"
portrule = function(host, port)
if
port.number == 1723
@@ -24,23 +26,6 @@ portrule = function(host, port)
end
action = function(host, port)
-- create the socket used for our connection
local socket = nmap.new_socket()
-- set a reasonable timeout value
socket:set_timeout(5000)
-- do some exception handling / cleanup
local catch = function()
socket:close()
end
local try = nmap.new_try(catch)
-- connect to the potential PPTP service
try(socket:connect(host.ip, port.number, "tcp"))
local payload
-- build a PPTP Start-Control-Connection-Request packet
@@ -67,23 +52,8 @@ action = function(host, port)
payload = payload .. "\000\000\000\000\000\000\000\000" -- padding for vendor name
payload = payload .. "\000\000\000\000" -- padding for vendor name
try(socket:send(payload))
local status
local response
-- read in any response we might get
status, response = socket:receive_bytes(1)
if (not status) then
return
end
if (response == "TIMEOUT") then
return
end
try(socket:close())
local try = nmap.new_try()
local response = try(comm.exchange(host, port, payload, {bytes=1, timeout=5000}))
local result

View File

@@ -8,20 +8,15 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"demo"}
require "comm"
require "shortport"
portrule = shortport.port_or_service(19, "chargen", "udp")
action = function(host, port)
local socket = nmap.new_socket()
socket:connect(host.ip, port.number, "udp")
socket:send("dummy")
local status, result = socket:receive_lines(1);
socket:close()
local status, result = comm.exchange(host, port, "dummy", {lines=1, proto="udp"})
if (result ~= nil) then
if status then
return "Chargen: success"
else
return "Chargen: something went wrong"
end
end

View File

@@ -8,18 +8,15 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"demo"}
require "comm"
require "shortport"
portrule = shortport.port_or_service(13, "daytime", "udp")
action = function(host, port)
local socket = nmap.new_socket()
socket:connect(host.ip, port.number, "udp")
socket:send("dummy")
local status, result = socket:receive_lines(1);
socket:close()
local status, result = comm.exchange(host, port, "dummy", {lines=1, proto="udp"})
if (result ~= nil) then
if status then
return "Daytime: " .. result
end
end

View File

@@ -9,6 +9,7 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "intrusive"}
require "bit"
require "comm"
require "shortport"
portrule = shortport.portnumber(53, "udp")
@@ -18,12 +19,11 @@ action = function(host, port)
-- generate dns query, Transaction-ID 0xdead, www.wikipedia.org (type A, class IN)
local request = string.char(0xde, 0xad, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03) .. "www" .. string.char(0x09) .. "wikipedia" .. string.char(0x03) .. "org" .. string.char(0x00, 0x00, 0x01, 0x00, 0x01)
local socket = nmap.new_socket()
socket:connect(host.ip, port.number, "udp")
socket:send(request)
local status, result = comm.exchange(host, port, request, {proto="udp"})
local status, result = socket:receive();
socket:close()
if not status then
return
end
-- parse response for dns flags
if (bit.band(string.byte(result,3), 0x80) == 0x80

View File

@@ -9,21 +9,17 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"demo"}
require "comm"
require "shortport"
portrule = shortport.port_or_service(7, "echo", "udp")
action = function(host, port)
local echostr = "hello there"
local socket = nmap.new_socket()
socket:connect(host.ip, port.number, "udp")
socket:send(echostr)
local status, result = socket:receive_lines(1);
socket:close()
local status, result = comm.exchange(host, port, echostr, {lines=1, proto="udp"})
if (result == echostr) then
return "UDP Echo: correct response"
end
return
end

View File

@@ -8,29 +8,13 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "discovery"}
require "comm"
require "shortport"
portrule = shortport.port_or_service(79, "finger")
action = function(host, port)
local socket = nmap.new_socket()
local results = ""
local status = true
local try = nmap.new_try()
local err_catch = function()
socket:close()
end
local try = nmap.new_try(err_catch())
socket:set_timeout(5000)
try(socket:connect(host.ip, port.number, port.protocol))
try(socket:send("\r\n"))
status, results = socket:receive_lines(100)
socket:close()
if not(status) then
return results
end
return try(comm.exchange(host, port, "\r\n", {lines=100, proto=port.protocol, timeout=5000}))
end

View File

@@ -9,24 +9,22 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"version"}
require "comm"
require "shortport"
portrule = shortport.portnumber(4569, "udp")
action = function(host, port)
local soc = nmap.new_socket()
soc:set_timeout(10000)
local conn = soc:connect(host.ip, port.number, port.protocol)
if (conn) then
-- see http://www.cornfed.com/iax.pdf for all options.
local poke = string.char(0x80, 0x00, 0x00, 0x00)
poke = poke .. string.char(0x00, 0x00, 0x00, 0x00)
poke = poke .. string.char(0x00, 0x00, 0x06, 0x1e)
soc:send(poke)
local status, recv
status, recv = soc:receive_bytes(1)
local status, recv = comm.exchange(host, port, poke, {bytes=1,proto=port.protocol,timeout=10000})
if not status then
return
end
if (string.len(recv)) == 12 then
local byte11 = string.format("%02X", string.byte(recv, 11))
@@ -43,9 +41,4 @@ action = function(host, port)
end
end
soc:close()
end
end

View File

@@ -9,23 +9,15 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"malware"}
require "comm"
require "shortport"
portrule = shortport.port_or_service(113, "auth")
action = function(host, port)
local status = 0
local owner = ""
local status, owner = comm.get_banner(host, port, {lines=1})
local client_ident = nmap.new_socket()
client_ident:connect(host.ip, port.number)
status, owner = client_ident:receive_lines(1)
client_ident:close()
if owner == "TIMEOUT" then
if not status then
return
end

View File

@@ -11,6 +11,8 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"}
require "comm"
-- I have excluded the port function param because it doesn't make much sense
-- for a hostrule. It works without warning. The NSE documentation is
-- not explicit enough in this regard.
@@ -49,51 +51,23 @@ end
-- Again, I have excluded the port param. Is this okay on a hostrule?
action = function(host)
local socket = nmap.new_socket()
socket:set_timeout(5000)
local result
local status = true
status, result = socket:connect(host.ip, 137, "udp")
if (not status) then
-- Can a UDP connect ever fail?
return
end
-- This is the UDP NetBIOS request packet. I didn't feel like
-- actually generating a new one each time so this has been shamelessly
-- copied from a packet dump of nbtscan.
-- See http://www.unixwiz.net/tools/nbtscan.html for code.
-- The magic number in this code is \003\097.
status, result = socket:send(
local data =
"\003\097\000\016\000\001\000\000" ..
"\000\000\000\000\032\067\075\065" ..
"\065\065\065\065\065\065\065\065" ..
"\065\065\065\065\065\065\065\065" ..
"\065\065\065\065\065\065\065\065" ..
"\065\065\065\065\065\000\000\033" ..
"\000\001")
"\000\001"
local status, result = comm.exchange(host, 137, data, {bytes=1, proto="udp", timeout=5000})
if (not status) then
-- Can the first UDP send ever fail?
return
end
-- this receive_bytes will consume all the input available
-- with a minimum of 1 byte.
status, result = socket:receive_bytes(1);
-- We don't need this socket anymore
socket:close()
if (not status) then
return
end
if (result == "TIMEOUT") then
return
end

View File

@@ -1,3 +1,4 @@
require "comm"
require "ipOps"
id = "RIPE query"
@@ -12,24 +13,11 @@ hostrule = function(host, port)
end
action = function(host, port)
local socket = nmap.new_socket()
local status, line
local result = ""
socket:connect("whois.ripe.net", 43)
-- socket:connect("193.0.0.135", 43)
socket:send(host.ip .. "\n")
while true do
local status, lines = socket:receive_lines(1)
local status, result = comm.exchange("whois.ripe.net", 43, host.ip .. "\n")
if not status then
break
else
result = result .. lines
return
end
end
socket:close()
local value = string.match(result, "role:(.-)\n")

View File

@@ -5,6 +5,7 @@ author = "Sven Klemm <sven@c3d2.de>"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default","safe","discovery"}
require "comm"
require "shortport"
require "packet"
require "datafiles"
@@ -12,14 +13,12 @@ require "datafiles"
portrule = shortport.port_or_service(111, "rpcbind")
action = function(host, port)
local try, catch
local try
local transaction_id = "nmap"
local socket = nmap.new_socket()
local result = " \n"
local rpc_numbers
catch = function() socket:close() end
try = nmap.new_try( catch )
try = nmap.new_try()
rpc_numbers = try(datafiles.parse_rpc())
local request = string.char(0x80,0,0,40) -- fragment header
@@ -29,16 +28,8 @@ action = function(host, port)
request = request .. "\0\0\0\2\0\0\0\4" -- programm version (2) procedure dump(4)
request = request .. "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"-- Credentials and verifier
socket:set_timeout(1000)
try( socket:connect(host.ip, port.number) )
try( socket:send( request ) )
local status, answer, answer_part
status, answer = socket:receive_bytes( 1 )
while status do
status, answer_part = socket:receive_bytes( 1 )
if status then answer = answer .. answer_part end
end
socket:close()
local answer = try(comm.exchange(host, port, request, {timeout=1000}))
local answer_part
local fragment_length = answer:byte(4) + answer:byte(3) * 256 + answer:byte(2) * 65536
if answer:sub(5,8) == transaction_id and answer:byte(12) == 1 and answer:byte(16) == 0 and answer:byte(28) == 0 then

View File

@@ -8,24 +8,18 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"demo"}
require "comm"
require "shortport"
portrule = shortport.port_or_service(25, "smtp")
action = function(host, port)
local status, result = comm.get_banner(host, port, {lines=1})
local client = nmap.new_socket()
client:connect(host.ip, port.number)
local status, result = client:receive_lines(1);
client:close()
if result ~= nil then
result = string.gsub(result, "\n", "")
if not status then
return
end
return result
return string.gsub(result, "\n", "")
end

View File

@@ -3,6 +3,7 @@ description="Determines if remote service is Skype protocol version 2"
author = "Brandon Enright <bmenrigh@ucsd.edu>"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"version"}
require "comm"
portrule = function(host, port)
if (port.number == 80 or
@@ -22,42 +23,25 @@ portrule = function(host, port)
end
action = function(host, port)
local socket = nmap.new_socket()
local result;
local status = true
socket:connect(host.ip, port.number, port.protocol)
socket:send("GET / HTTP/1.0\r\n\r\n")
status, result = socket:receive_bytes(26);
local status, result = comm.exchange(host, port, "GET / HTTP/1.0\r\n\r\n", {bytes=26, proto=port.protocol})
if (not status) then
socket:close()
return
end
if (result ~= "HTTP/1.0 404 Not Found\r\n\r\n") then
socket:close()
return
end
socket:close();
-- So far so good, now see if we get random data for another request
socket:connect(host.ip, port.number, port.protocol)
socket:send("random data\r\n\r\n")
status, result = socket:receive_bytes(15);
status, result = comm.exchange(host, port, "random data\r\n\r\n", {bytes=15, proto=port.protocol})
if (not status) then
socket:close()
return
end
if string.match(result, "[^%s!-~].*[^%s!-~].*[^%s!-~]") then
socket:close()
port.version.name = "skype2"
port.version.product = "Skype"
port.version.confidence = 10
@@ -67,7 +51,5 @@ action = function(host, port)
-- return "Skype v2 server detected"
end
socket:close();
return
end