mirror of
https://github.com/nmap/nmap.git
synced 2026-02-11 07:56:35 +00:00
Merge from /nmap-exp/david/nmap-unconnected. This adds unconnected
socket support to NSE, with updates in scripts and libraries.
o [NSE] Added the ability to send and receive on unconnected sockets.
This can be used, for example, to receive UDP broadcasts without
using pcap. A number of scripts have been changed so that they can
work as prerule scripts to discover services by UDP broadcasting,
optionally add the discovered targets to the scanning queue:
- ms-sql-info
- upnp-info
- dns-service-discovery
The nmap.new_socket function can now optionally take a default
protocol and address family, which will be used if the socket is not
connected. There is a new nmap.sendto function to be used with
unconnected UDP sockets. [David, Patrik]
This commit is contained in:
13
CHANGELOG
13
CHANGELOG
@@ -1,5 +1,18 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added the ability to send and receive on unconnected sockets.
|
||||
This can be used, for example, to receive UDP broadcasts without
|
||||
using pcap. A number of scripts have been changed so that they can
|
||||
work as prerule scripts to discover services by UDP broadcasting,
|
||||
optionally add the discovered targets to the scanning queue:
|
||||
- ms-sql-info
|
||||
- upnp-info
|
||||
- dns-service-discovery
|
||||
The nmap.new_socket function can now optionally take a default
|
||||
protocol and address family, which will be used if the socket is not
|
||||
connected. There is a new nmap.sendto function to be used with
|
||||
unconnected UDP sockets. [David, Patrik]
|
||||
|
||||
o [NSE] Improved ssh2's kex_init() parameters: all of the algorithm
|
||||
and language lists can be set using new keys in the "options" table
|
||||
argument. These all default to the same value used before. Also, the
|
||||
|
||||
107
nse_nsock.cc
107
nse_nsock.cc
@@ -47,6 +47,9 @@ typedef struct nse_nsock_udata
|
||||
|
||||
lua_State *thread;
|
||||
|
||||
int proto;
|
||||
int af;
|
||||
|
||||
const char *direction;
|
||||
const char *action;
|
||||
|
||||
@@ -77,6 +80,7 @@ static nsock_pool new_pool (lua_State *L)
|
||||
{
|
||||
nsock_pool nsp = nsp_new(NULL);
|
||||
nsock_pool *nspp;
|
||||
nsp_setbroadcast(nsp, true);
|
||||
lua_pushlightuserdata(L, &NSOCK_POOL);
|
||||
nspp = (nsock_pool *) lua_newuserdata(L, sizeof(nsock_pool));
|
||||
*nspp = nsp;
|
||||
@@ -426,8 +430,35 @@ static nse_nsock_udata *check_nsock_udata (lua_State *L, int idx, int open)
|
||||
{
|
||||
nse_nsock_udata *nu =
|
||||
(nse_nsock_udata *) luaL_checkudata(L, idx, NMAP_NSOCK_SOCKET);
|
||||
if (open && nu->nsiod == NULL)
|
||||
luaL_error(L, "socket must be connected\n");
|
||||
|
||||
if (open && nu->nsiod == NULL) {
|
||||
/* The socket hasn't been connected or setup yet. Try doing a setup, or
|
||||
throw and error if that's not possible. */
|
||||
if (nu->proto == IPPROTO_UDP) {
|
||||
nsock_pool nsp;
|
||||
|
||||
nsp = get_pool(L);
|
||||
nu->nsiod = nsi_new(nsp, NULL);
|
||||
if (nu->source_addr.ss_family != AF_UNSPEC) {
|
||||
nsi_set_localaddr(nu->nsiod, &nu->source_addr, nu->source_addrlen);
|
||||
} else if (o.spoofsource) {
|
||||
struct sockaddr_storage ss;
|
||||
size_t sslen;
|
||||
o.SourceSockAddr(&ss, &sslen);
|
||||
nsi_set_localaddr(nu->nsiod, &ss, sslen);
|
||||
}
|
||||
if (o.ipoptionslen)
|
||||
nsi_set_ipoptions(nu->nsiod, o.ipoptions, o.ipoptionslen);
|
||||
|
||||
if (nsock_setup_udp(nsp, nu->nsiod, nu->af) == -1) {
|
||||
luaL_error(L, "Error in setup of iod with proto %d and af %d: %s (%d)",
|
||||
nu->proto, nu->af, socket_strerror(socket_errno()), socket_errno());
|
||||
}
|
||||
} else {
|
||||
luaL_error(L, "socket must be connected\n");
|
||||
}
|
||||
}
|
||||
|
||||
return nu;
|
||||
}
|
||||
|
||||
@@ -470,7 +501,19 @@ static int l_connect (lua_State *L)
|
||||
const char *addr, *targetname; check_target(L, 2, &addr, &targetname);
|
||||
const char *default_proto = NULL;
|
||||
unsigned short port = check_port(L, 3, &default_proto);
|
||||
if (default_proto == NULL) default_proto = "tcp";
|
||||
if (default_proto == NULL) {
|
||||
switch (nu->proto) {
|
||||
case IPPROTO_TCP:
|
||||
default_proto = "tcp";
|
||||
break;
|
||||
case IPPROTO_UDP:
|
||||
default_proto = "udp";
|
||||
break;
|
||||
default:
|
||||
default_proto = "tcp";
|
||||
break;
|
||||
}
|
||||
}
|
||||
int what = luaL_checkoption(L, 4, default_proto, op);
|
||||
struct addrinfo *dest;
|
||||
int error_id;
|
||||
@@ -512,17 +555,22 @@ static int l_connect (lua_State *L)
|
||||
fatal("nsi_set_hostname(\"%s\" failed in %s()", targetname, __func__);
|
||||
}
|
||||
|
||||
nu->af = dest->ai_addr->sa_family;
|
||||
|
||||
switch (what)
|
||||
{
|
||||
case TCP:
|
||||
nu->proto = IPPROTO_TCP;
|
||||
nsock_connect_tcp(nsp, nu->nsiod, callback, nu->timeout, nu,
|
||||
dest->ai_addr, dest->ai_addrlen, port);
|
||||
break;
|
||||
case UDP:
|
||||
nu->proto = IPPROTO_UDP;
|
||||
nsock_connect_udp(nsp, nu->nsiod, callback, nu, dest->ai_addr,
|
||||
dest->ai_addrlen, port);
|
||||
break;
|
||||
case SSL:
|
||||
nu->proto = IPPROTO_TCP;
|
||||
nsock_connect_ssl(nsp, nu->nsiod, callback, nu->timeout, nu,
|
||||
dest->ai_addr, dest->ai_addrlen, IPPROTO_TCP, port, nu->ssl_session);
|
||||
break;
|
||||
@@ -543,6 +591,31 @@ static int l_send (lua_State *L)
|
||||
return yield(L, nu, "SEND", TO, 0, NULL);
|
||||
}
|
||||
|
||||
static int l_sendto (lua_State *L)
|
||||
{
|
||||
nsock_pool nsp = get_pool(L);
|
||||
nse_nsock_udata *nu = check_nsock_udata(L, 1, 1);
|
||||
size_t size;
|
||||
const char *addr, *targetname; check_target(L, 2, &addr, &targetname);
|
||||
const char *default_proto = NULL;
|
||||
unsigned short port = check_port(L, 3, &default_proto);
|
||||
const char *string = luaL_checklstring(L, 4, &size);
|
||||
int error_id;
|
||||
struct addrinfo *dest;
|
||||
|
||||
error_id = getaddrinfo(addr, NULL, NULL, &dest);
|
||||
if (error_id)
|
||||
return safe_error(L, gai_strerror(error_id));
|
||||
|
||||
if (dest == NULL)
|
||||
return safe_error(L, "getaddrinfo returned success but no addresses");
|
||||
|
||||
nsock_sendto(nsp, nu->nsiod, callback, nu->timeout, nu, dest->ai_addr, dest->ai_addrlen, port, string, size);
|
||||
trace(nu->nsiod, hexify((unsigned char *) string, size).c_str(), TO);
|
||||
return yield(L, nu, "SEND", TO, 0, NULL);
|
||||
|
||||
}
|
||||
|
||||
static void receive_callback (nsock_pool nsp, nsock_event nse, void *udata)
|
||||
{
|
||||
nse_nsock_udata *nu = (nse_nsock_udata *) udata;
|
||||
@@ -771,13 +844,25 @@ static int l_bind (lua_State *L)
|
||||
return success(L);
|
||||
}
|
||||
|
||||
static void initialize (lua_State *L, int idx, nse_nsock_udata *nu)
|
||||
static const char *default_af_string(int af)
|
||||
{
|
||||
if (af == AF_INET)
|
||||
return "inet";
|
||||
else
|
||||
return "inet6";
|
||||
}
|
||||
|
||||
static void initialize (lua_State *L, int idx, nse_nsock_udata *nu,
|
||||
int proto, int af)
|
||||
{
|
||||
|
||||
lua_createtable(L, 2, 0); /* room for thread in array */
|
||||
lua_pushliteral(L, "");
|
||||
lua_rawseti(L, -2, BUFFER_I);
|
||||
lua_setfenv(L, idx);
|
||||
nu->nsiod = NULL;
|
||||
nu->proto = proto;
|
||||
nu->af = af;
|
||||
nu->ssl_session = NULL;
|
||||
nu->source_addr.ss_family = AF_UNSPEC;
|
||||
nu->source_addrlen = sizeof(nu->source_addr);
|
||||
@@ -789,13 +874,22 @@ static void initialize (lua_State *L, int idx, nse_nsock_udata *nu)
|
||||
|
||||
LUALIB_API int l_nsock_new (lua_State *L)
|
||||
{
|
||||
static const char *proto_strings[] = { "tcp", "udp", NULL };
|
||||
int proto_map[] = { IPPROTO_TCP, IPPROTO_UDP };
|
||||
static const char *af_strings[] = { "inet", "inet6", NULL };
|
||||
int af_map[] = { AF_INET, AF_INET6 };
|
||||
int proto, af;
|
||||
nse_nsock_udata *nu;
|
||||
|
||||
proto = proto_map[luaL_checkoption(L, 1, "tcp", proto_strings)];
|
||||
af = af_map[luaL_checkoption(L, 2, default_af_string(o.af()), af_strings)];
|
||||
|
||||
lua_settop(L, 0);
|
||||
|
||||
nu = (nse_nsock_udata *) lua_newuserdata(L, sizeof(nse_nsock_udata));
|
||||
luaL_getmetatable(L, NMAP_NSOCK_SOCKET);
|
||||
lua_setmetatable(L, -2);
|
||||
initialize(L, 1, nu);
|
||||
initialize(L, 1, nu, proto, af);
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -812,7 +906,7 @@ static int l_close (lua_State *L)
|
||||
#endif
|
||||
if (!nu->is_pcap) /* pcap sockets are closed by pcap_gc */
|
||||
nsi_delete(nu->nsiod, NSOCK_PENDING_NOTIFY);
|
||||
initialize(L, 1, nu);
|
||||
initialize(L, 1, nu, nu->proto, nu->af);
|
||||
return success(L);
|
||||
}
|
||||
|
||||
@@ -989,6 +1083,7 @@ LUALIB_API int luaopen_nsock (lua_State *L)
|
||||
static const luaL_Reg l_nsock[] = {
|
||||
{"bind", l_bind},
|
||||
{"send", l_send},
|
||||
{"sendto", l_sendto},
|
||||
{"receive", l_receive},
|
||||
{"receive_lines", l_receive_lines},
|
||||
{"receive_bytes", l_receive_bytes},
|
||||
|
||||
@@ -360,11 +360,9 @@ local function dhcp_send(interface, host, packet, transaction_id)
|
||||
local results = {}
|
||||
|
||||
|
||||
-- Create a pcap socket to listen for the response. I used to consider this a hack, but
|
||||
-- it really isn't -- it's kinda how this has to be done.
|
||||
local pcap = nmap.new_socket()
|
||||
pcap:pcap_open(interface, 590, false, "udp port 68")
|
||||
pcap:set_timeout(5000)
|
||||
local bind_socket = nmap.new_socket("udp")
|
||||
bind_socket:bind(nil, 68)
|
||||
bind_socket:set_timeout(5000)
|
||||
stdnse.print_debug(1, "dhcp: Starting listener")
|
||||
|
||||
-- Create the UDP socket (TODO: enable SO_BROADCAST if we need to)
|
||||
@@ -379,15 +377,15 @@ local function dhcp_send(interface, host, packet, transaction_id)
|
||||
socket:send(packet)
|
||||
|
||||
-- Read the response
|
||||
local status, length, layer2, layer3 = pcap:pcap_receive();
|
||||
local status, data = bind_socket:receive()
|
||||
-- This pulls back 4 bytes in the packet that correspond to the transaction id. This should be randomly
|
||||
-- generated and different for every instance of a script (to prevent collisions)
|
||||
while status and layer3:sub(33, 36) ~= transaction_id do
|
||||
status, length, layer2, layer3 = pcap:pcap_receive();
|
||||
end
|
||||
while status and data:sub(5, 8) ~= transaction_id do
|
||||
local status, data = bind_socket:receive()
|
||||
end
|
||||
if(status == false) then
|
||||
stdnse.print_debug(1, "dhcp: Error calling pcap_receive(): %s", err)
|
||||
return false, "Error calling pcap_receive(): " .. err
|
||||
stdnse.print_debug(1, "dhcp: Error calling bind_socket:receive(): %s", err)
|
||||
return false, "Error calling bind_socket:receive(): " .. err
|
||||
end
|
||||
|
||||
-- If no data was captured (ie, a timeout), return an error
|
||||
@@ -396,12 +394,9 @@ local function dhcp_send(interface, host, packet, transaction_id)
|
||||
return false, "TIMEOUT"
|
||||
end
|
||||
|
||||
-- Cut off the address/transport headers
|
||||
data = string.sub(data, 29) -- I doubt this is the right way to do this, but since we're only supporting IPv4 + UDP, maybe it'll work
|
||||
|
||||
-- Close our sockets
|
||||
socket:close()
|
||||
pcap:close()
|
||||
bind_socket:close()
|
||||
|
||||
-- Finally, return the data
|
||||
return true, data
|
||||
|
||||
138
nselib/dns.lua
138
nselib/dns.lua
@@ -40,21 +40,51 @@ types = {
|
||||
-- @param port Port to connect to.
|
||||
-- @param timeout Number of ms to wait for a response.
|
||||
-- @param cnt Number of tries.
|
||||
-- @param multiple If true, keep reading multiple responses until timeout.
|
||||
-- @return Status (true or false).
|
||||
-- @return Response (if status is true).
|
||||
local function sendPackets(data, host, port, timeout, cnt)
|
||||
local socket = nmap.new_socket()
|
||||
socket:set_timeout(timeout)
|
||||
socket:connect(host, port, "udp")
|
||||
local function sendPackets(data, host, port, timeout, cnt, multiple)
|
||||
local socket = nmap.new_socket("udp")
|
||||
local responses = {}
|
||||
|
||||
socket:set_timeout(timeout)
|
||||
|
||||
if ( not(multiple) ) then
|
||||
socket:connect( host, port, "udp" )
|
||||
end
|
||||
|
||||
for i = 1, cnt do
|
||||
local status, err
|
||||
|
||||
if ( multiple ) then
|
||||
status, err = socket:sendto(host, port, data)
|
||||
else
|
||||
status, err = socket:send(data)
|
||||
end
|
||||
|
||||
if (not(status)) then return false, err end
|
||||
|
||||
for i = 1, cnt do
|
||||
socket:send(data)
|
||||
local response
|
||||
local status, response = socket:receive_bytes(1)
|
||||
|
||||
if (status) then
|
||||
|
||||
if ( multiple ) then
|
||||
while(true) do
|
||||
status, response = socket:receive()
|
||||
if( not(status) ) then break end
|
||||
|
||||
local status, _, _, ip, _ = socket:get_info()
|
||||
table.insert(responses, { data = response, peer = ip } )
|
||||
end
|
||||
else
|
||||
status, response = socket:receive()
|
||||
if ( status ) then
|
||||
local status, _, _, ip, _ = socket:get_info()
|
||||
table.insert(responses, { data = response, peer = ip } )
|
||||
end
|
||||
end
|
||||
|
||||
if (#responses>0) then
|
||||
socket:close()
|
||||
return true, response
|
||||
return true, responses
|
||||
end
|
||||
end
|
||||
socket:close()
|
||||
@@ -130,6 +160,42 @@ local function getAuthDns(rPkt)
|
||||
return false
|
||||
end
|
||||
|
||||
local function processResponse( response, dname, dtype, options )
|
||||
|
||||
local rPkt = decode(response)
|
||||
-- is it a real answer?
|
||||
if gotAnswer(rPkt) then
|
||||
if (options.retPkt) then
|
||||
return true, rPkt
|
||||
else
|
||||
return findNiceAnswer(dtype, rPkt, options.retAll)
|
||||
end
|
||||
else -- if not, ask the next server in authority
|
||||
|
||||
local next_server = getAuthDns(rPkt)
|
||||
|
||||
-- if we got a CNAME, ask for the CNAME
|
||||
if type(next_server) == 'table' and next_server.cname then
|
||||
options.tries = option.tries - 1
|
||||
return query(next_server.cname, options)
|
||||
end
|
||||
|
||||
-- only ask next server in authority, if
|
||||
-- we got an auth dns and
|
||||
-- it isn't the one we just asked
|
||||
if next_server and next_server ~= host and options.tries > 1 then
|
||||
options.host = next_server
|
||||
options.tries = option.tries - 1
|
||||
return query(dname, options)
|
||||
end
|
||||
end
|
||||
|
||||
-- nothing worked
|
||||
stdnse.print_debug(1, "dns.query() failed to resolve the requested query%s%s", dname and ": " or ".", dname or "")
|
||||
return false, "No Answers"
|
||||
|
||||
end
|
||||
|
||||
---
|
||||
-- Query DNS servers for a DNS record.
|
||||
-- @param dname Desired domain name entry.
|
||||
@@ -141,6 +207,7 @@ end
|
||||
-- * <code>retAll</code>: Return all answers, not just the first.
|
||||
-- * <code>retPkt</code>: Return the packet instead of using the answer-fetching mechanism.
|
||||
-- * <code>norecurse</code> If true, do not set the recursion (RD) flag.
|
||||
-- * <code>multiple</code> If true, expects multiple hosts to respond to multicast request
|
||||
-- @return True if a dns response was received and contained an answer of the requested type,
|
||||
-- or the decoded dns response was requested (retPkt) and is being returned - or False otherwise.
|
||||
-- @return String answer of the requested type, Table of answers or a String error message of one of the following:
|
||||
@@ -148,9 +215,9 @@ end
|
||||
function query(dname, options)
|
||||
if not options then options = {} end
|
||||
|
||||
local dtype, host, port, tries = options.dtype, options.host, options.port, options.tries
|
||||
local dtype, host, port = options.dtype, options.host, options.port
|
||||
|
||||
if not tries then tries = 10 end -- don't get into an infinite loop
|
||||
if not options.tries then options.tries = 10 end -- don't get into an infinite loop
|
||||
|
||||
if not options.sendCount then options.sendCount = 2 end
|
||||
|
||||
@@ -182,7 +249,7 @@ function query(dname, options)
|
||||
|
||||
local data = encode(pkt)
|
||||
|
||||
local status, response = sendPackets(data, host, port, options.timeout, options.sendCount)
|
||||
local status, response = sendPackets(data, host, port, options.timeout, options.sendCount, options.multiple)
|
||||
|
||||
|
||||
-- if working with know nameservers, try the others
|
||||
@@ -194,37 +261,18 @@ function query(dname, options)
|
||||
|
||||
-- if we got any response:
|
||||
if status then
|
||||
local rPkt = decode(response)
|
||||
-- is it a real answer?
|
||||
if gotAnswer(rPkt) then
|
||||
if (options.retPkt) then
|
||||
return true, rPkt
|
||||
else
|
||||
return findNiceAnswer(dtype, rPkt, options.retAll)
|
||||
end
|
||||
else -- if not, ask the next server in authority
|
||||
|
||||
local next_server = getAuthDns(rPkt)
|
||||
|
||||
-- if we got a CNAME, ask for the CNAME
|
||||
if type(next_server) == 'table' and next_server.cname then
|
||||
options.tries = tries - 1
|
||||
return query(next_server.cname, options)
|
||||
end
|
||||
|
||||
-- only ask next server in authority, if
|
||||
-- we got an auth dns and
|
||||
-- it isn't the one we just asked
|
||||
if next_server and next_server ~= host and tries > 1 then
|
||||
options.host = next_server
|
||||
options.tries = tries - 1
|
||||
return query(dname, options)
|
||||
end
|
||||
end
|
||||
|
||||
-- nothing worked
|
||||
stdnse.print_debug(1, "dns.query() failed to resolve the requested query%s%s", dname and ": " or ".", dname or "")
|
||||
return false, "No Answers"
|
||||
if ( options.multiple ) then
|
||||
local multiresponse = {}
|
||||
for _, r in ipairs( response ) do
|
||||
local status, presponse = processResponse( r.data, dname, dtype, options )
|
||||
if( status ) then
|
||||
table.insert( multiresponse, { ['output']=presponse, ['peer']=r.peer } )
|
||||
end
|
||||
end
|
||||
return true, multiresponse
|
||||
else
|
||||
return processResponse( response[1].data, dname, dtype, options)
|
||||
end
|
||||
else
|
||||
stdnse.print_debug(1, "dns.query() got zero responses attempting to resolve query%s%s", dname and ": " or ".", dname or "")
|
||||
return false, "No Answers"
|
||||
@@ -348,7 +396,7 @@ answerFetcher[types.CNAME] = function(dec, retAll)
|
||||
stdnse.print_debug(1, "dns.answerFetcher found no records of the required type: NS, PTR or CNAME")
|
||||
return false, "No Answers"
|
||||
end
|
||||
return true, answers
|
||||
return true, answers
|
||||
end
|
||||
|
||||
-- Answer fetcher for MX records.
|
||||
|
||||
@@ -323,10 +323,12 @@ function new_try(handler)
|
||||
-- NSE sockets are the recommended way to do network I/O. They support
|
||||
-- <code>connect</code>-style sending and receiving over TCP and UDP (and SSL),
|
||||
-- as well as raw socket receiving.
|
||||
-- @param protocol a protocol string (optional, defaults to <code>"tcp"</code>).
|
||||
-- @param af an address family string (optional, defaults to <code>"inet"</code>).
|
||||
-- @return A new NSE socket.
|
||||
-- @see pcap_open
|
||||
-- @usage local socket = nmap.new_socket()
|
||||
function new_socket()
|
||||
function new_socket(protocol, af)
|
||||
|
||||
--- Sets the local address of a socket.
|
||||
--
|
||||
@@ -385,7 +387,8 @@ function bind(addr, port)
|
||||
-- @param host Host table, hostname or IP address.
|
||||
-- @param port Port table or number.
|
||||
-- @param protocol <code>"tcp"</code>, <code>"udp"</code>, or
|
||||
-- <code>"ssl"</code> (default <code>"tcp"</code>).
|
||||
-- <code>"ssl"</code> (default <code>"tcp"</code>, or whatever was set in
|
||||
-- <code>new_socket</code>).
|
||||
-- @return Status (true or false).
|
||||
-- @return Error code (if status is false).
|
||||
-- @see new_socket
|
||||
@@ -428,6 +431,30 @@ function reconnect_ssl()
|
||||
-- @usage local status, err = socket:send(data)
|
||||
function send(data)
|
||||
|
||||
--- Sends data on an unconnected socket to a given destination.
|
||||
--
|
||||
-- Sockets that have not been connected do not have an implicit
|
||||
-- destination address, so the <code>send</code> function doesn't work. Instead
|
||||
-- the destination must be given with each send using this function. The
|
||||
-- protocol and address family of the socket must have been set in
|
||||
-- <code>new_socket</code>. On
|
||||
-- success the function returns a true value. If the send operation fails, the
|
||||
-- function returns a false value (<code>false</code> or <code>nil</code>) along
|
||||
-- with an error string. The error strings are
|
||||
-- * <code>"Trying to send through a closed socket"</code>: There was no call to <code>socket:connect</code> before the send operation.
|
||||
-- * <code>"TIMEOUT"</code>: The operation took longer than the specified timeout for the socket.
|
||||
-- * <code>"ERROR"</code>: An error occurred inside the underlying Nsock library.
|
||||
-- * <code>"CANCELLED"</code>: The operation was cancelled.
|
||||
-- * <code>"KILL"</code>: For example the script scan is aborted due to a faulty script.
|
||||
-- * <code>"EOF"</code>: An EOF was read (probably will not occur for a send operation).
|
||||
-- @param host The hostname or IP address to send to.
|
||||
-- @param port The port number to send to.
|
||||
-- @param data The data to send.
|
||||
-- @return Status (true or false).
|
||||
-- @return Error code (if status is false).
|
||||
-- @usage local status, err = socket:send(data)
|
||||
function sendto(host, port, data)
|
||||
|
||||
--- Receives data from an open socket.
|
||||
--
|
||||
-- The receive method does a non-blocking receive operation on an open socket.
|
||||
|
||||
133
scripts/db2-discover.nse
Normal file
133
scripts/db2-discover.nse
Normal file
@@ -0,0 +1,133 @@
|
||||
description = [[
|
||||
Attempts do discover DB2 servers on the network using UDP
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- sudo ./nmap -sU -p 523 --script db2-discover <ip>
|
||||
--
|
||||
-- @output
|
||||
-- PORT STATE SERVICE
|
||||
-- 523/udp open ibm-db2
|
||||
-- | db2-discover:
|
||||
-- | 10.0.200.132 (UBU804-DB2E) - IBM DB2 v9.07.0
|
||||
-- |_ 10.0.200.119 (EDUSRV011) - IBM DB2 v9.07.0
|
||||
|
||||
-- Version 0.1
|
||||
-- Created 08/27/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||
-- Revised 10/10/2010 - v0.2 - add prerule, newtargets <patrik@cqure.net>
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"safe", "discovery"}
|
||||
|
||||
require "stdnse"
|
||||
require "shortport"
|
||||
require "target"
|
||||
|
||||
prerule = function() return true end
|
||||
portrule = shortport.version_port_or_service(523, "ibm-db2", "udp",
|
||||
{"open", "open|filtered"})
|
||||
|
||||
|
||||
--- Converts the prodrel server string to a version string
|
||||
--
|
||||
-- @param server_version string containing the product release
|
||||
-- @return ver string containing the version information
|
||||
local function parseVersion( server_version )
|
||||
local pfx = string.sub(server_version,1,3)
|
||||
|
||||
if pfx == "SQL" then
|
||||
local major_version = string.sub(server_version,4,5)
|
||||
|
||||
-- strip the leading 0 from the major version, for consistency with
|
||||
-- nmap-service-probes results
|
||||
if string.sub(major_version,1,1) == "0" then
|
||||
major_version = string.sub(major_version,2)
|
||||
end
|
||||
local minor_version = string.sub(server_version,6,7)
|
||||
local hotfix = string.sub(server_version,8)
|
||||
server_version = major_version .. "." .. minor_version .. "." .. hotfix
|
||||
else
|
||||
return "Unknown version"
|
||||
end
|
||||
|
||||
return ("IBM DB2 v%s"):format(server_version)
|
||||
end
|
||||
|
||||
preaction = function()
|
||||
|
||||
local DB2GETADDR = "DB2GETADDR\0SQL09010\0"
|
||||
local socket = nmap.new_socket("udp")
|
||||
local result = {}
|
||||
local host, port = "255.255.255.255", 523
|
||||
|
||||
socket:set_timeout(5000)
|
||||
local status = socket:sendto( host, port, DB2GETADDR )
|
||||
if ( not(status) ) then return end
|
||||
|
||||
while(true) do
|
||||
local data
|
||||
status, data = socket:receive()
|
||||
if( not(status) ) then break end
|
||||
|
||||
local version, srvname = data:match("DB2RETADDR.(SQL%d+).(.-)%z")
|
||||
local _, ip
|
||||
status, _, _, ip, _ = socket:get_info()
|
||||
if ( not(status) ) then return end
|
||||
|
||||
if target.ALLOW_NEW_TARGETS then target.add(ip) end
|
||||
|
||||
if ( status ) then
|
||||
table.insert( result, ("%s - Host: %s; Version: %s"):format(ip, srvname, parseVersion( version ) ) )
|
||||
end
|
||||
end
|
||||
socket:close()
|
||||
|
||||
return stdnse.format_output( true, result )
|
||||
end
|
||||
|
||||
scanaction = function(host, port)
|
||||
|
||||
local DB2GETADDR = "DB2GETADDR\0SQL09010\0"
|
||||
local socket = nmap.new_socket()
|
||||
local result = {}
|
||||
|
||||
socket:set_timeout(5000)
|
||||
|
||||
local status, err = socket:connect( host, port, "udp")
|
||||
if ( not(status) ) then return end
|
||||
|
||||
status, err = socket:send( DB2GETADDR )
|
||||
if ( not(status) ) then return end
|
||||
|
||||
local data
|
||||
status, data = socket:receive()
|
||||
if( not(status) ) then
|
||||
socket:close()
|
||||
return
|
||||
end
|
||||
|
||||
local version, srvname = data:match("DB2RETADDR.(SQL%d+).(.-)%z")
|
||||
|
||||
if ( status ) then
|
||||
table.insert( result, ("Host: %s"):format(srvname) )
|
||||
table.insert( result, ("Version: %s"):format(parseVersion(version)) )
|
||||
end
|
||||
|
||||
socket:close()
|
||||
-- set port to open
|
||||
nmap.set_port_state(host, port, "open")
|
||||
|
||||
return stdnse.format_output( true, result )
|
||||
end
|
||||
|
||||
|
||||
-- Function dispatch table
|
||||
local actions = {
|
||||
prerule = preaction,
|
||||
hostrule = scanaction,
|
||||
portrule = scanaction,
|
||||
}
|
||||
|
||||
function action (...) return actions[SCRIPT_TYPE](...) end
|
||||
@@ -33,10 +33,13 @@ get more information.
|
||||
-- |_ Address=192.168.0.2 fe80:0:0:0:223:6cff:1234:5678
|
||||
|
||||
|
||||
-- Version 0.3
|
||||
-- Version 0.6
|
||||
-- Created 01/06/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||
-- Revised 01/13/2010 - v0.2 - modified to use existing dns library instead of mdns, changed output to be less DNS like
|
||||
-- Revised 02/01/2010 - v0.3 - removed incorrect try/catch statements
|
||||
-- Revised 10/04/2010 - v0.4 - added prerule and add target support <patrik@cqure.net>
|
||||
-- Revised 10/05/2010 - v0.5 - added ip sort function and
|
||||
-- Revised 10/10/2010 - v0.6 - multicast queries are now used in parallel to collect service information <patrik@cqure.net>
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
@@ -44,8 +47,10 @@ categories = {"default", "discovery", "safe"}
|
||||
|
||||
require 'shortport'
|
||||
require 'dns'
|
||||
require 'target'
|
||||
|
||||
portrule = shortport.portnumber(5353, "udp")
|
||||
prerule = function() return true end
|
||||
|
||||
--- Gets a record from both the Answer and Additional section
|
||||
--
|
||||
@@ -95,8 +100,10 @@ end
|
||||
-- @param b table containing second item
|
||||
-- @return true if the port of a is less than the port of b
|
||||
local function serviceCompare(a, b)
|
||||
local port_a = a.name:match("^(%d+)") or 0
|
||||
local port_b = b.name:match("^(%d+)") or 0
|
||||
-- if no port is found use 999999 for comparing, this way all services
|
||||
-- without ports and device information gets printed at the end
|
||||
local port_a = a.name:match("^(%d+)") or 999999
|
||||
local port_b = b.name:match("^(%d+)") or 999999
|
||||
|
||||
if ( tonumber(port_a) < tonumber(port_b) ) then
|
||||
return true
|
||||
@@ -104,97 +111,239 @@ local function serviceCompare(a, b)
|
||||
return false
|
||||
end
|
||||
|
||||
action = function(host, port)
|
||||
--- Converts a string ip to a numeric value suitable for comparing
|
||||
--
|
||||
-- @param ip string containing the ip to convert
|
||||
-- @return number containing the converted ip
|
||||
local function ipToNumber(ip)
|
||||
local o1, o2, o3, o4 = ip:match("^(%d*)%.(%d*)%.(%d*)%.(%d*)$")
|
||||
return (256^3) * o1 + (256^2) * o2 + (256^1) * o3 + (256^0) * o4
|
||||
end
|
||||
|
||||
local result = {}
|
||||
local deviceinfo = {}
|
||||
local status, response = dns.query( "_services._dns-sd._udp.local", { port = 5353, host = host.ip, dtype="PTR", retAll=true} )
|
||||
--- Compare function used for sorting IP-addresses
|
||||
--
|
||||
-- @param a table containing first item
|
||||
-- @param b table containing second item
|
||||
-- @return true if the port of a is less than the port of b
|
||||
local function ipCompare(a, b)
|
||||
local ip_a = ipToNumber(a.name) or 0
|
||||
local ip_b = ipToNumber(b.name) or 0
|
||||
|
||||
if not status then
|
||||
if ( tonumber(ip_a) < tonumber(ip_b) ) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Send a query for a particular service and store the response in a table
|
||||
--
|
||||
-- @param host string containing the ip to connect to
|
||||
-- @param port number containing the port to connect to
|
||||
-- @param svc the service record to retrieve
|
||||
-- @param multiple true if responses from multiple hosts are expected
|
||||
-- @param svcresponse table to which results are stored
|
||||
local function queryService( host, port, svc, multiple, svcresponse )
|
||||
local condvar = nmap.condvar(svcresponse)
|
||||
local status, response = dns.query( svc, { port = port, host = host, dtype="PTR", retPkt=true, retAll=true, multiple=multiple, sendCount=1, timeout=2000} )
|
||||
if not status then
|
||||
stdnse.print_debug("Failed to query service: %s; Error: %s", svc, response)
|
||||
return
|
||||
end
|
||||
svcresponse[svc] = svcresponse[svc] or {}
|
||||
if ( multiple ) then
|
||||
for _, r in ipairs(response) do
|
||||
table.insert( svcresponse[svc], r )
|
||||
end
|
||||
else
|
||||
svcresponse[svc] = response
|
||||
end
|
||||
condvar("broadcast")
|
||||
end
|
||||
|
||||
--- Sends a unicast query for each discovered service to each host
|
||||
--
|
||||
-- @param host string containing the ip to connect to
|
||||
-- @param record string containing the DNS record to query
|
||||
-- @param result table to which the results are added
|
||||
local function processRecords( response, result )
|
||||
local service, deviceinfo = {}, {}
|
||||
local txt = {}
|
||||
local ip, ipv6, srv, address, port, proto
|
||||
|
||||
-- for each service response in answers, send a service query
|
||||
for _, v in ipairs( response ) do
|
||||
local record = ( #response.questions > 0 and response.questions[1].dname ) and response.questions[1].dname or ""
|
||||
|
||||
local service = {}
|
||||
local txt = {}
|
||||
local ip, ipv6, srv, address, port, proto
|
||||
|
||||
status, response = dns.query( v, { port = 5353, host = host.ip, dtype="PTR", retPkt=true} )
|
||||
|
||||
if not status then
|
||||
return
|
||||
end
|
||||
|
||||
status, ip = getRecordType( dns.types.A, response, false )
|
||||
|
||||
if status then
|
||||
address = ip
|
||||
end
|
||||
|
||||
status, ipv6 = getRecordType( dns.types.AAAA, response, false )
|
||||
|
||||
if status then
|
||||
address = address .. " " .. ipv6
|
||||
end
|
||||
|
||||
status, txt = getRecordType( dns.types.TXT, response, true )
|
||||
|
||||
if status then
|
||||
for _, v in ipairs(txt) do
|
||||
if v:len() > 0 then
|
||||
table.insert(service, v)
|
||||
end
|
||||
status, ip = getRecordType( dns.types.A, response, false )
|
||||
if status then address = ip end
|
||||
|
||||
status, ipv6 = getRecordType( dns.types.AAAA, response, false )
|
||||
if status then address = address .. " " .. ipv6 end
|
||||
|
||||
status, txt = getRecordType( dns.types.TXT, response, true )
|
||||
if status then
|
||||
for _, v in ipairs(txt) do
|
||||
if v:len() > 0 then
|
||||
table.insert(service, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
status, srv = getRecordType( dns.types.SRV, response, false )
|
||||
if status then
|
||||
local srvparams = stdnse.strsplit( ":", srv )
|
||||
|
||||
status, srv = getRecordType( dns.types.SRV, response, false )
|
||||
|
||||
if status then
|
||||
local srvparams = stdnse.strsplit( ":", srv )
|
||||
if #srvparams > 3 then
|
||||
port = srvparams[3]
|
||||
end
|
||||
end
|
||||
|
||||
if #srvparams > 3 then
|
||||
port = srvparams[3]
|
||||
if address then
|
||||
table.insert( service, ("Address=%s"):format( address ) )
|
||||
end
|
||||
|
||||
if record == "_device-info._tcp.local" then
|
||||
service.name = "Device Information"
|
||||
deviceinfo = service
|
||||
table.insert(result, deviceinfo)
|
||||
else
|
||||
local serviceparams = stdnse.strsplit("[.]", record)
|
||||
|
||||
if #serviceparams > 2 then
|
||||
local servicename = serviceparams[1]:sub(2)
|
||||
local proto = serviceparams[2]:sub(2)
|
||||
|
||||
if port == nil or proto == nil or servicename == nil then
|
||||
service.name = record
|
||||
else
|
||||
service.name = string.format( "%s/%s %s", port, proto, servicename)
|
||||
end
|
||||
end
|
||||
|
||||
if address then
|
||||
table.insert( service, ("Address=%s"):format( address ) )
|
||||
end
|
||||
table.insert( result, service )
|
||||
end
|
||||
|
||||
if v == "_device-info._tcp.local" then
|
||||
service.name = "Device Information"
|
||||
deviceinfo = service
|
||||
end
|
||||
|
||||
|
||||
--- Returns the amount of currenlty active threads
|
||||
--
|
||||
-- @param threads table containing the list of threads
|
||||
-- @return count number containing the number of non-dead threads
|
||||
threadCount = function( threads )
|
||||
local count = 0
|
||||
|
||||
for thread in pairs(threads) do
|
||||
if ( coroutine.status(thread) == "dead" ) then
|
||||
threads[thread] = nil
|
||||
else
|
||||
local serviceparams = stdnse.strsplit("[.]", v)
|
||||
|
||||
if #serviceparams > 2 then
|
||||
local servicename = serviceparams[1]:sub(2)
|
||||
local proto = serviceparams[2]:sub(2)
|
||||
|
||||
if port == nil or proto == nil or servicename == nil then
|
||||
service.name = v
|
||||
else
|
||||
service.name = string.format( "%s/%s %s", port, proto, servicename)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert( result, service )
|
||||
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--- Creates a service host table
|
||||
--
|
||||
-- ['_ftp._tcp.local'] = {10.10.10.10,20.20.20.20}
|
||||
-- ['_http._tcp.local'] = {30.30.30.30,40.40.40.40}
|
||||
--
|
||||
-- @param response containing the response from <code>dns.query</code>
|
||||
-- @return services table containing the service name as a key and all host addresses as value
|
||||
local function createSvcHostTbl( response )
|
||||
local services = {}
|
||||
-- Create unique table of services
|
||||
for _, r in ipairs( response ) do
|
||||
for _, svc in ipairs(r.output ) do
|
||||
services[svc] = services[svc] or {}
|
||||
table.insert(services[svc], r.peer)
|
||||
end
|
||||
end
|
||||
|
||||
return services
|
||||
end
|
||||
|
||||
preaction = function()
|
||||
local result = {}
|
||||
local host, port = "224.0.0.251", 5353
|
||||
local status, response = dns.query( "_services._dns-sd._udp.local", { port = port, host = host, dtype="PTR", retAll=true, multiple=true, sendCount=1, timeout=2000} )
|
||||
if not status then return end
|
||||
|
||||
local services = createSvcHostTbl(response)
|
||||
local ipsvctbl = {}
|
||||
local svcresponse = {}
|
||||
local condvar = nmap.condvar( svcresponse )
|
||||
local threads = {}
|
||||
|
||||
-- Start one collector thread for each service
|
||||
for svc in pairs(services) do
|
||||
local co = stdnse.new_thread( queryService, host, port, svc, true, svcresponse )
|
||||
threads[co] = true
|
||||
end
|
||||
|
||||
-- Wait for all threads to finish running
|
||||
while threadCount(threads)>0 do
|
||||
condvar("wait")
|
||||
end
|
||||
|
||||
-- Process all records that were returned
|
||||
for svcname, response in pairs(svcresponse) do
|
||||
for _, r in ipairs( response ) do
|
||||
ipsvctbl[r.peer] = ipsvctbl[r.peer] or {}
|
||||
processRecords( r.output, ipsvctbl[r.peer] )
|
||||
end
|
||||
end
|
||||
|
||||
-- Restructure and build our output table
|
||||
for ip, svctbl in pairs( ipsvctbl ) do
|
||||
table.sort(svctbl, serviceCompare)
|
||||
svctbl.name = ip
|
||||
if target.ALLOW_NEW_TARGETS then target.add(ip) end
|
||||
table.insert( result, svctbl )
|
||||
end
|
||||
table.sort( result, ipCompare )
|
||||
|
||||
return stdnse.format_output(true, result )
|
||||
end
|
||||
|
||||
scanaction = function(host, port)
|
||||
local result = {}
|
||||
local status, response = dns.query( "_services._dns-sd._udp.local", { port = 5353, host = host.ip, dtype="PTR", retAll=true, sendCount=1, timeout=2000 } )
|
||||
if not status then return end
|
||||
|
||||
local svcresponse = {}
|
||||
local condvar = nmap.condvar( svcresponse )
|
||||
local threads = {}
|
||||
|
||||
-- Start one collector thread for each service
|
||||
for _, svc in ipairs(response) do
|
||||
local co = stdnse.new_thread( queryService, host.ip, port, svc, false, svcresponse )
|
||||
threads[co] = true
|
||||
end
|
||||
|
||||
-- Wait for all threads to finish running
|
||||
while threadCount(threads)>0 do
|
||||
condvar("wait")
|
||||
end
|
||||
|
||||
-- Process all records that were returned
|
||||
for svcname, response in pairs(svcresponse) do
|
||||
processRecords( response, result )
|
||||
end
|
||||
|
||||
-- sort the tables per port
|
||||
table.sort( result, serviceCompare )
|
||||
|
||||
-- we want the device information at the end
|
||||
table.insert( result, deviceinfo )
|
||||
|
||||
|
||||
-- set port to open
|
||||
nmap.set_port_state(host, port, "open")
|
||||
|
||||
return stdnse.format_output(true, result )
|
||||
|
||||
|
||||
end
|
||||
|
||||
-- Function dispatch table
|
||||
local actions = {
|
||||
prerule = preaction,
|
||||
hostrule = scanaction,
|
||||
portrule = scanaction,
|
||||
}
|
||||
|
||||
function action (...) return actions[SCRIPT_TYPE](...) end
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ Attempts to extract information from Microsoft SQL Server instances.
|
||||
]]
|
||||
-- rev 1.0 (2007-06-09)
|
||||
-- rev 1.1 (2009-12-06 - Added SQL 2008 identification T Sellers)
|
||||
-- rev 1.2 (2010-10-03 - Added Broadcast support <patrik@cqure.net>)
|
||||
-- rev 1.3 (2010-10-10 - Added prerule and newtargets support <patrik@cqure.net>)
|
||||
|
||||
author = "Thomas Buchanan"
|
||||
|
||||
@@ -20,147 +22,36 @@ categories = {"default", "discovery", "intrusive"}
|
||||
-- | Instance name: SQLEXPRESS
|
||||
-- | TCP Port: 1433
|
||||
-- |_ Could not retrieve actual version information
|
||||
--
|
||||
-- PORT STATE SERVICE
|
||||
-- 1434/udp open|filtered ms-sql-m
|
||||
-- | ms-sql-info:
|
||||
-- | 10.0.200.133
|
||||
-- | Discovered Microsoft SQL Server 2008 Express Edition
|
||||
-- | Server name: WIN2K3-EPI-1
|
||||
-- | Server version: 10.0.1600.22 (RTM)
|
||||
-- | Instance name: SQLEXPRESS
|
||||
-- | TCP Port: 1052
|
||||
-- | Could not retrieve actual version information
|
||||
-- | 10.0.200.119
|
||||
-- | Discovered Microsoft SQL Server 2000
|
||||
-- | Server name: EDUSRV011
|
||||
-- | Instance name: MSSQLSERVER
|
||||
-- | TCP Port: 1433
|
||||
-- | Could not retrieve actual version information
|
||||
-- | Instance name: SQLEXPRESS
|
||||
-- | TCP Port: 1433
|
||||
-- |_ Could not retrieve actual version information
|
||||
|
||||
require('stdnse')
|
||||
require "stdnse"
|
||||
require "shortport"
|
||||
require("strbuf")
|
||||
require "strbuf"
|
||||
require "target"
|
||||
|
||||
prerule = function() return true end
|
||||
portrule = shortport.portnumber({1433, 1434}, "udp", {"open", "open|filtered"})
|
||||
|
||||
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)
|
||||
|
||||
-- try to login to MS SQL network service, and obtain the real version information
|
||||
-- MS SQL 2000 does not report the correct version in the data sent in response to UDP probe (see below)
|
||||
local get_real_version = function(dst, dstPort)
|
||||
|
||||
local outcome
|
||||
local payload = strbuf.new()
|
||||
|
||||
local stat, resp
|
||||
|
||||
-- build a TDS packet - type 0x12
|
||||
-- copied from packet capture of osql connection
|
||||
payload = payload .. "\018\001\000\047\000\000\001\000\000\000"
|
||||
payload = payload .. "\026\000\006\001\000\032\000\001\002\000"
|
||||
payload = payload .. "\033\000\001\003\000\034\000\004\004\000"
|
||||
payload = payload .. "\038\000\001\255\009\000\011\226\000\000"
|
||||
payload = payload .. "\000\000\120\023\000\000\000"
|
||||
|
||||
socket = nmap.new_socket()
|
||||
|
||||
-- connect to the server using the tcpPort captured from the UDP probe
|
||||
try(socket:connect(dst, dstPort, "tcp"))
|
||||
|
||||
try(socket:send(strbuf.dump(payload)))
|
||||
|
||||
-- read in any response we might get
|
||||
stat, resp = socket:receive_bytes(1)
|
||||
|
||||
if string.match(resp, "^\004") then
|
||||
|
||||
-- build a login packet to send to SQL server
|
||||
-- username = sa, blank password
|
||||
-- for information about packet structure, see http://www.freetds.org/tds.html
|
||||
|
||||
local query = strbuf.new()
|
||||
query = query .. "\016\001\000\128\000\000\001\000" -- TDS packet header
|
||||
query = query .. "\120\000\000\000\002\000\009\114" -- Login packet header = length, version
|
||||
query = query .. "\000\000\000\000\000\000\000\007" -- Login packet header continued = size, client version
|
||||
query = query .. "\140\018\000\000\000\000\000\000" -- Login packet header continued = Client PID, Connection ID
|
||||
query = query .. "\224\003\000\000\104\001\000\000" -- Login packet header continued = Option Flags 1 & 2, status flag, reserved flag, timezone
|
||||
query = query .. "\009\004\000\000\094\000\004\000" -- Login packet (Collation), then start offsets & lengths (client name, client length)
|
||||
query = query .. "\102\000\002\000\000\000\000\000" -- Login packet, offsets & lengths = username offset, username length, password offset, password length
|
||||
query = query .. "\106\000\004\000\114\000\000\000" -- Login packet, offsets & lengths = app name offset, app name length, server name offset, server name length
|
||||
query = query .. "\000\000\000\000\114\000\003\000" -- Login packet, offsets & lengths = unknown offset, unknown length, library name offset, library name length
|
||||
query = query .. "\120\000\000\000\120\000\000\000" -- Login packet, offsets & lengths = locale offset, locale length, database name offset, database name length
|
||||
query = query .. "\000\000\000\000\000\000\000\000" -- Login packet, MAC address + padding
|
||||
query = query .. "\000\000\000\000\000\000\000\000" -- Login packet, padding
|
||||
query = query .. "\000\000\000\000\000\000\078\000" -- Login packet, padding + start of client name (N)
|
||||
query = query .. "\077\000\065\000\080\000\115\000" -- Login packet = rest of client name (MAP) + username (s)
|
||||
query = query .. "\097\000\078\000\077\000\065\000" -- Login packet = username (a), app name (NMA)
|
||||
query = query .. "\080\000\078\000\083\000\069\000" -- Login packet = app name (P), library name (NSE)
|
||||
|
||||
-- send the packet down the wire
|
||||
try(socket:send(strbuf.dump(query)))
|
||||
|
||||
-- read in any response we might get
|
||||
stat, resp = socket:receive_bytes(1)
|
||||
|
||||
-- successful response to login packet should contain the string "SQL Server"
|
||||
-- however, the string is UCS2 encoded, so we have to add the \000 characters
|
||||
if string.match(resp, "S\000Q\000L\000") then
|
||||
outcome = "\n sa user appears to have blank password"
|
||||
|
||||
strbuf.clear(query)
|
||||
-- since we have a successful login, send a query that will tell us what version the server is really running
|
||||
query = query .. "\001\001\000\044\000\000\001\000" -- TDS Query packet
|
||||
query = query .. "\083\000\069\000\076\000\069\000" -- SELE
|
||||
query = query .. "\067\000\084\000\032\000\064\000" -- CT @
|
||||
query = query .. "\064\000\086\000\069\000\082\000" -- @VER
|
||||
query = query .. "\083\000\073\000\079\000\078\000" -- SION
|
||||
query = query .. "\013\000\010\000"
|
||||
|
||||
-- send the packet down the wire
|
||||
try(socket:send(strbuf.dump(query)))
|
||||
|
||||
-- read in any response we might get
|
||||
stat, resp = socket:receive_bytes(1)
|
||||
|
||||
-- strip out the embedded \000 characters
|
||||
local banner = string.gsub(resp, "%z", "")
|
||||
outcome = outcome .. "\n " .. string.match(banner, "(Microsoft.-)\n")
|
||||
outcome = outcome .. "\n" .. string.match(banner, "\n.-\n.-\n(.-Build.-)\n")
|
||||
end
|
||||
|
||||
try(socket:close())
|
||||
|
||||
end -- if string.match(response, "^\004")
|
||||
|
||||
if outcome == nil then
|
||||
outcome = "\n Could not retrieve actual version information"
|
||||
end
|
||||
|
||||
return outcome
|
||||
end -- get_real_version(dst, dstPort)
|
||||
|
||||
-- connect to the potential SQL server
|
||||
try(socket:connect(host, port))
|
||||
|
||||
-- send a magic packet
|
||||
-- details here: http://www.codeproject.com/cs/database/locate_sql_servers.asp
|
||||
try(socket:send("\002"))
|
||||
|
||||
local status
|
||||
local response
|
||||
|
||||
-- read in any response we might get
|
||||
status, response = socket:receive_bytes(1)
|
||||
|
||||
try(socket:close())
|
||||
|
||||
if (not status) then
|
||||
return
|
||||
end
|
||||
|
||||
if (response == "TIMEOUT") then
|
||||
return
|
||||
end
|
||||
|
||||
-- since we got something back, the port is definitely open
|
||||
nmap.set_port_state(host, port, "open")
|
||||
local function process_response( response )
|
||||
|
||||
local result
|
||||
|
||||
@@ -185,74 +76,266 @@ action = function(host, port)
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
result = {}
|
||||
|
||||
-- do some heuristics on the version to see if we can match the major releases
|
||||
if string.match(serverInfo[1].version, "^6%.0") then
|
||||
result = "Discovered Microsoft SQL Server 6.0"
|
||||
table.insert(result, "Discovered Microsoft SQL Server 6.0")
|
||||
elseif string.match(serverInfo[1].version, "^6%.5") then
|
||||
result = "Discovered Microsoft SQL Server 6.5"
|
||||
table.insert(result, "Discovered Microsoft SQL Server 6.5")
|
||||
elseif string.match(serverInfo[1].version, "^7%.0") then
|
||||
result = "Discovered Microsoft SQL Server 7.0"
|
||||
table.insert(result, "Discovered Microsoft SQL Server 7.0")
|
||||
elseif string.match(serverInfo[1].version, "^8%.0") then
|
||||
result = "Discovered Microsoft SQL Server 2000"
|
||||
table.insert(result, "Discovered Microsoft SQL Server 2000")
|
||||
elseif string.match(serverInfo[1].version, "^9%.0") then
|
||||
-- The Express Edition of MS SQL Server 2005 has a default instance name of SQLEXPRESS
|
||||
for _,instance in ipairs(serverInfo) do
|
||||
if string.match(instance.instanceName, "SQLEXPRESS") then
|
||||
result = "Discovered Microsoft SQL Server 2005 Express Edition"
|
||||
table.insert(result, "Discovered Microsoft SQL Server 2005 Express Edition")
|
||||
end
|
||||
end
|
||||
if result == nil then
|
||||
result = "Discovered Microsoft SQL Server 2005"
|
||||
table.insert(result, "Discovered Microsoft SQL Server 2005")
|
||||
end
|
||||
elseif string.match(serverInfo[1].version, "^10%.0") then
|
||||
-- The Express Edition of MS SQL Server 2008 has a default instance name of SQLEXPRESS
|
||||
for _,instance in ipairs(serverInfo) do
|
||||
if string.match(instance.instanceName, "SQLEXPRESS") then
|
||||
result = "Discovered Microsoft SQL Server 2008 Express Edition"
|
||||
table.insert(result, "Discovered Microsoft SQL Server 2008 Express Edition")
|
||||
end
|
||||
end
|
||||
if result == nil then
|
||||
result = "Discovered Microsoft SQL Server 2008"
|
||||
table.insert(result, "Discovered Microsoft SQL Server 2008")
|
||||
end
|
||||
else
|
||||
result = "Discovered Microsoft SQL Server"
|
||||
table.insert(result, "Discovered Microsoft SQL Server")
|
||||
end
|
||||
if serverInfo[1].name ~= nil then
|
||||
result = result .. "\n Server name: " .. serverInfo[1].name
|
||||
table.insert(result, "Server name: " .. serverInfo[1].name)
|
||||
end
|
||||
if serverInfo[1].version ~= nil then
|
||||
result = result .. "\n Server version: " .. serverInfo[1].version
|
||||
--result = result .. "\n Server version: " .. serverInfo[1].version
|
||||
-- Check for some well known release versions of SQL Server 2005
|
||||
-- for more info, see http://support.microsoft.com/kb/321185
|
||||
if string.match(serverInfo[1].version, "9.00.3042") then
|
||||
result = result .. " (SP2)"
|
||||
table.insert(result, "Server version: " .. serverInfo[1].version .. " (SP2)" )
|
||||
elseif string.match(serverInfo[1].version, "9.00.3043") then
|
||||
result = result .. " (SP2)"
|
||||
table.insert(result, "Server version: " .. serverInfo[1].version .. " (SP2)" )
|
||||
elseif string.match(serverInfo[1].version, "9.00.2047") then
|
||||
result = result .. " (SP1)"
|
||||
table.insert(result, "Server version: " .. serverInfo[1].version .. " (SP1)" )
|
||||
elseif string.match(serverInfo[1].version, "9.00.1399") then
|
||||
result = result .. " (RTM)"
|
||||
table.insert(result, "Server version: " .. serverInfo[1].version .. " (RTM)" )
|
||||
-- Check for versions of SQL Server 2008
|
||||
elseif string.match(serverInfo[1].version, "10.0.1075") then
|
||||
result = result .. " (CTP)"
|
||||
table.insert(result, "Server version: " .. serverInfo[1].version .. " (CTP)" )
|
||||
elseif string.match(serverInfo[1].version, "10.0.1600") then
|
||||
result = result .. " (RTM)"
|
||||
table.insert(result, "Server version: " .. serverInfo[1].version .. " (RTM)" )
|
||||
elseif string.match(serverInfo[1].version, "10.0.2531") then
|
||||
result = result .. " (SP1)"
|
||||
table.insert(result, "Server version: " .. serverInfo[1].version .. " (SP1)" )
|
||||
end
|
||||
end
|
||||
for _,instance in ipairs(serverInfo) do
|
||||
if instance.instanceName ~= nil then
|
||||
result = result .. "\n Instance name: " .. instance.instanceName
|
||||
end
|
||||
if instance.tcpPort ~= nil then
|
||||
result = result .. "\n TCP Port: " .. instance.tcpPort
|
||||
result = result .. get_real_version(host.ip, instance.tcpPort)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
return result, serverInfo
|
||||
end
|
||||
|
||||
-- try to login to MS SQL network service, and obtain the real version information
|
||||
-- MS SQL 2000 does not report the correct version in the data sent in response to UDP probe (see below)
|
||||
local get_real_version = function(dst, dstPort)
|
||||
|
||||
local outcome = {}
|
||||
local payload = strbuf.new()
|
||||
local stat, resp
|
||||
|
||||
-- do some exception handling / cleanup
|
||||
local catch = function() socket:close() end
|
||||
local try = nmap.new_try(catch)
|
||||
|
||||
|
||||
-- build a TDS packet - type 0x12
|
||||
-- copied from packet capture of osql connection
|
||||
payload = payload .. "\018\001\000\047\000\000\001\000\000\000"
|
||||
payload = payload .. "\026\000\006\001\000\032\000\001\002\000"
|
||||
payload = payload .. "\033\000\001\003\000\034\000\004\004\000"
|
||||
payload = payload .. "\038\000\001\255\009\000\011\226\000\000"
|
||||
payload = payload .. "\000\000\120\023\000\000\000"
|
||||
|
||||
socket = nmap.new_socket()
|
||||
|
||||
-- connect to the server using the tcpPort captured from the UDP probe
|
||||
try(socket:connect(dst, dstPort, "tcp"))
|
||||
|
||||
try(socket:send(strbuf.dump(payload)))
|
||||
|
||||
-- read in any response we might get
|
||||
stat, resp = socket:receive_bytes(1)
|
||||
|
||||
if string.match(resp, "^\004") then
|
||||
|
||||
-- build a login packet to send to SQL server
|
||||
-- username = sa, blank password
|
||||
-- for information about packet structure, see http://www.freetds.org/tds.html
|
||||
|
||||
local query = strbuf.new()
|
||||
query = query .. "\016\001\000\128\000\000\001\000" -- TDS packet header
|
||||
query = query .. "\120\000\000\000\002\000\009\114" -- Login packet header = length, version
|
||||
query = query .. "\000\000\000\000\000\000\000\007" -- Login packet header continued = size, client version
|
||||
query = query .. "\140\018\000\000\000\000\000\000" -- Login packet header continued = Client PID, Connection ID
|
||||
query = query .. "\224\003\000\000\104\001\000\000" -- Login packet header continued = Option Flags 1 & 2, status flag, reserved flag, timezone
|
||||
query = query .. "\009\004\000\000\094\000\004\000" -- Login packet (Collation), then start offsets & lengths (client name, client length)
|
||||
query = query .. "\102\000\002\000\000\000\000\000" -- Login packet, offsets & lengths = username offset, username length, password offset, password length
|
||||
query = query .. "\106\000\004\000\114\000\000\000" -- Login packet, offsets & lengths = app name offset, app name length, server name offset, server name length
|
||||
query = query .. "\000\000\000\000\114\000\003\000" -- Login packet, offsets & lengths = unknown offset, unknown length, library name offset, library name length
|
||||
query = query .. "\120\000\000\000\120\000\000\000" -- Login packet, offsets & lengths = locale offset, locale length, database name offset, database name length
|
||||
query = query .. "\000\000\000\000\000\000\000\000" -- Login packet, MAC address + padding
|
||||
query = query .. "\000\000\000\000\000\000\000\000" -- Login packet, padding
|
||||
query = query .. "\000\000\000\000\000\000\078\000" -- Login packet, padding + start of client name (N)
|
||||
query = query .. "\077\000\065\000\080\000\115\000" -- Login packet = rest of client name (MAP) + username (s)
|
||||
query = query .. "\097\000\078\000\077\000\065\000" -- Login packet = username (a), app name (NMA)
|
||||
query = query .. "\080\000\078\000\083\000\069\000" -- Login packet = app name (P), library name (NSE)
|
||||
|
||||
-- send the packet down the wire
|
||||
try(socket:send(strbuf.dump(query)))
|
||||
|
||||
-- read in any response we might get
|
||||
stat, resp = socket:receive_bytes(1)
|
||||
|
||||
-- successful response to login packet should contain the string "SQL Server"
|
||||
-- however, the string is UCS2 encoded, so we have to add the \000 characters
|
||||
if string.match(resp, "S\000Q\000L\000") then
|
||||
table.insert( outcome, "sa user appears to have blank password" )
|
||||
|
||||
strbuf.clear(query)
|
||||
-- since we have a successful login, send a query that will tell us what version the server is really running
|
||||
query = query .. "\001\001\000\044\000\000\001\000" -- TDS Query packet
|
||||
query = query .. "\083\000\069\000\076\000\069\000" -- SELE
|
||||
query = query .. "\067\000\084\000\032\000\064\000" -- CT @
|
||||
query = query .. "\064\000\086\000\069\000\082\000" -- @VER
|
||||
query = query .. "\083\000\073\000\079\000\078\000" -- SION
|
||||
query = query .. "\013\000\010\000"
|
||||
|
||||
-- send the packet down the wire
|
||||
try(socket:send(strbuf.dump(query)))
|
||||
|
||||
-- read in any response we might get
|
||||
stat, resp = socket:receive_bytes(1)
|
||||
|
||||
-- strip out the embedded \000 characters
|
||||
local banner = string.gsub(resp, "%z", "")
|
||||
table.insert( outcome, string.match(banner, "(Microsoft.-)\n") )
|
||||
table.insert( outcome, string.match(banner, "\n.-\n.-\n(.-Build.-)\n") )
|
||||
end
|
||||
|
||||
try(socket:close())
|
||||
|
||||
end -- if string.match(response, "^\004")
|
||||
|
||||
if #outcome == 0 then
|
||||
table.insert( outcome, "Could not retrieve actual version information" )
|
||||
end
|
||||
|
||||
return outcome
|
||||
end -- get_real_version(dst, dstPort)
|
||||
|
||||
|
||||
preaction = function()
|
||||
|
||||
local host, port = "255.255.255.255", 1434
|
||||
-- create the socket used for our connection
|
||||
local socket = nmap.new_socket("udp")
|
||||
-- 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)
|
||||
|
||||
-- send a magic packet
|
||||
-- details here: http://www.codeproject.com/cs/database/locate_sql_servers.asp
|
||||
try(socket:sendto(host, port, "\002"))
|
||||
|
||||
local output = {}
|
||||
|
||||
while(true) do
|
||||
-- read in any response we might get
|
||||
local status, response = socket:receive()
|
||||
if ( not(status) ) then break end
|
||||
|
||||
local status, _, _, ip, _ = socket:get_info()
|
||||
if ( not(status) ) then return end
|
||||
|
||||
local result, serverInfo = process_response( response )
|
||||
for _,instance in ipairs(serverInfo) do
|
||||
if instance.instanceName ~= nil then
|
||||
table.insert(result, "Instance name: " .. instance.instanceName)
|
||||
end
|
||||
if instance.tcpPort ~= nil then
|
||||
table.insert(result, "TCP Port: " .. instance.tcpPort)
|
||||
table.insert(result, get_real_version(ip, instance.tcpPort) )
|
||||
end
|
||||
end
|
||||
|
||||
if target.ALLOW_NEW_TARGETS then
|
||||
target.add(ip)
|
||||
end
|
||||
|
||||
result.name = ip
|
||||
table.insert( output, result )
|
||||
end
|
||||
|
||||
socket:close()
|
||||
return stdnse.format_output( true, output )
|
||||
|
||||
end
|
||||
|
||||
scanaction = 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)
|
||||
|
||||
try(socket:connect(host, port, "udp"))
|
||||
|
||||
-- send a magic packet
|
||||
-- details here: http://www.codeproject.com/cs/database/locate_sql_servers.asp
|
||||
try(socket:send("\002"))
|
||||
|
||||
-- read in any response we might get
|
||||
local status, response = socket:receive()
|
||||
if (not(status)) then
|
||||
socket:close()
|
||||
return
|
||||
end
|
||||
|
||||
local _, _, ip, _ = try(socket:get_info())
|
||||
|
||||
local result, serverInfo = process_response( response )
|
||||
for _,instance in ipairs(serverInfo) do
|
||||
if instance.instanceName ~= nil then
|
||||
table.insert(result, "Instance name: " .. instance.instanceName)
|
||||
end
|
||||
if instance.tcpPort ~= nil then
|
||||
table.insert(result, "TCP Port: " .. instance.tcpPort)
|
||||
table.insert(result, get_real_version(ip, instance.tcpPort) )
|
||||
end
|
||||
end
|
||||
socket:close()
|
||||
|
||||
nmap.set_port_state( host, port, "open")
|
||||
return stdnse.format_output( true, result )
|
||||
end
|
||||
|
||||
-- Function dispatch table
|
||||
local actions = {
|
||||
prerule = preaction,
|
||||
hostrule = scanaction,
|
||||
portrule = scanaction,
|
||||
}
|
||||
|
||||
function action (...) return actions[SCRIPT_TYPE](...) end
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ Attempts to extract system information from the UPnP service.
|
||||
-- | upnp-info: System/1.0 UPnP/1.0 IGD/1.0
|
||||
-- |_ Location: http://192.168.1.1:80/UPnP/IGD.xml
|
||||
|
||||
-- 2010-10-05 - add prerule support <patrik@cqure.net>
|
||||
-- 2010-10-10 - add newtarget support <patrik@cqure.net>
|
||||
|
||||
author = "Thomas Buchanan"
|
||||
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
@@ -16,69 +19,29 @@ categories = {"default", "safe"}
|
||||
require("stdnse")
|
||||
require("shortport")
|
||||
require("strbuf")
|
||||
require("target")
|
||||
|
||||
prerule = function() return true end
|
||||
|
||||
---
|
||||
-- Runs on UDP port 1900
|
||||
portrule = shortport.portnumber(1900, "udp", {"open", "open|filtered"})
|
||||
|
||||
---
|
||||
-- Sends UPnP discovery packet to host,
|
||||
-- and extracts service information from results
|
||||
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 function process_response( response )
|
||||
|
||||
local catch = function() socket:close() end
|
||||
local try = nmap.new_try(catch)
|
||||
|
||||
-- connect to the potential UPnP system
|
||||
try(socket:connect(host, port))
|
||||
|
||||
local payload = strbuf.new()
|
||||
|
||||
-- for details about the UPnP message format, see http://upnp.org/resources/documents.asp
|
||||
payload = payload .. "M-SEARCH * HTTP/1.1\r\n"
|
||||
payload = payload .. "Host:239.255.255.250:1900\r\n"
|
||||
payload = payload .. "ST:upnp:rootdevice\r\n"
|
||||
payload = payload .. "Man:\"ssdp:discover\"\r\n"
|
||||
payload = payload .. "MX:3\r\n\r\n"
|
||||
|
||||
try(socket:send(strbuf.dump(payload)))
|
||||
|
||||
local status
|
||||
local response
|
||||
|
||||
-- read in any response we might get
|
||||
status, response = socket:receive_bytes(1)
|
||||
|
||||
if (not status) or (response == "TIMEOUT") then
|
||||
socket:close()
|
||||
return
|
||||
end
|
||||
|
||||
-- since we got something back, the port is definitely open
|
||||
nmap.set_port_state(host, port, "open")
|
||||
|
||||
-- buffer to hold script output
|
||||
local output
|
||||
local output = {}
|
||||
|
||||
if response ~= nil then
|
||||
-- We should get a response back that has contains one line for the server, and one line for the xml file location
|
||||
-- these match any combination of upper and lower case responses
|
||||
local server, location
|
||||
server = string.match(response, "[Ss][Ee][Rr][Vv][Ee][Rr]:(.-)\010")
|
||||
if server ~= nil then output = server .. "\n" end
|
||||
server = string.match(response, "[Ss][Ee][Rr][Vv][Ee][Rr]:%s*(.-)\010")
|
||||
if server ~= nil then table.insert(output, server ) end
|
||||
location = string.match(response, "[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:(.-)\010")
|
||||
if location ~= nil then
|
||||
output = output .. "Location: " .. location
|
||||
table.insert(output, "Location: " .. location )
|
||||
|
||||
local v = nmap.verbosity()
|
||||
|
||||
@@ -98,17 +61,14 @@ action = function(host, port)
|
||||
xport = 80
|
||||
end
|
||||
|
||||
-- check if the IP address in the location matches the IP address we're scanning
|
||||
-- if not, alert the user, but continue to scan the IP address we're interested in
|
||||
if xhost ~= host.ip then
|
||||
output = output .. "\n !! Location did not match target IP address !! "
|
||||
-- return output
|
||||
xhost = host.ip
|
||||
end
|
||||
|
||||
local peer = {}
|
||||
local _
|
||||
|
||||
-- extract the path name from the location field, but strip off the \r that HTTP servers return
|
||||
xfile = string.match(location, "http://.-/(.-)\013")
|
||||
if xfile ~= nil then
|
||||
local payload = strbuf.new()
|
||||
|
||||
strbuf.clear(payload)
|
||||
-- create an HTTP request for the file, using the host and port we extracted earlier
|
||||
payload = payload .. "GET /" .. xfile .. " HTTP/1.1\r\n"
|
||||
@@ -119,7 +79,7 @@ action = function(host, port)
|
||||
payload = payload .. "Cache-Control: no-cache\r\n"
|
||||
payload = payload .. "Pragma: no-cache\r\n\r\n"
|
||||
|
||||
socket = nmap.new_socket()
|
||||
local socket = nmap.new_socket()
|
||||
socket:set_timeout(5000)
|
||||
|
||||
try(socket:connect(xhost, xport, "tcp"))
|
||||
@@ -132,7 +92,7 @@ action = function(host, port)
|
||||
local webserver
|
||||
-- extract information about the webserver that is handling responses for the UPnP system
|
||||
webserver = string.match(response, "[Ss][Ee][Rr][Vv][Ee][Rr]:(.-)\010")
|
||||
if webserver ~= nil then output = output .. "\nWebserver: " .. webserver end
|
||||
if webserver ~= nil then table.insert(output, "Webserver: " .. webserver) end
|
||||
|
||||
-- the schema for UPnP includes a number of <device> entries, which can a number of interesting fields
|
||||
for device in string.gmatch(response, "<deviceType>(.-)</UDN>") do
|
||||
@@ -144,11 +104,11 @@ action = function(host, port)
|
||||
nm = string.match(device, "<modelName>(.-)</modelName>")
|
||||
ver = string.match(device, "<modelNumber>(.-)</modelNumber>")
|
||||
|
||||
if fn ~= nil then output = output .. "\n Name: " .. fn end
|
||||
if mnf ~= nil then output = output .. "\n Manufacturer: " .. mnf end
|
||||
if mdl ~= nil then output = output .. "\n Model Descr: " .. mdl end
|
||||
if nm ~= nil then output = output .. "\n Model Name: " .. nm end
|
||||
if ver ~= nil then output = output .. "\n Model Version: " .. ver end
|
||||
if fn ~= nil then table.insert(output, "Name: " .. fn) end
|
||||
if mnf ~= nil then table.insert(output,"Manufacturer: " .. mnf) end
|
||||
if mdl ~= nil then table.insert(output,"Model Descr: " .. mdl) end
|
||||
if nm ~= nil then table.insert(output,"Model Name: " .. nm) end
|
||||
if ver ~= nil then table.insert(output,"Model Version: " .. ver) end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -160,3 +120,122 @@ action = function(host, port)
|
||||
return output
|
||||
end
|
||||
end
|
||||
|
||||
--- Converts a string ip to a numeric value suitable for comparing
|
||||
--
|
||||
-- @param ip string containing the ip to convert
|
||||
-- @return number containing the converted ip
|
||||
local function ipToNumber(ip)
|
||||
local o1, o2, o3, o4 = ip:match("^(%d*)%.(%d*)%.(%d*)%.(%d*)$")
|
||||
return (256^3) * o1 + (256^2) * o2 + (256^1) * o3 + (256^0) * o4
|
||||
end
|
||||
|
||||
--- Compare function used for sorting IP-addresses
|
||||
--
|
||||
-- @param a table containing first item
|
||||
-- @param b table containing second item
|
||||
-- @return true if the port of a is less than the port of b
|
||||
local function ipCompare(a, b)
|
||||
local ip_a = ipToNumber(a.name)
|
||||
local ip_b = ipToNumber(b.name)
|
||||
if ( tonumber(ip_a) < tonumber(ip_b) ) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
---
|
||||
-- Sends UPnP discovery packet to host,
|
||||
-- and extracts service information from results
|
||||
preaction = function(host, port)
|
||||
|
||||
-- create the socket used for our connection
|
||||
local socket = nmap.new_socket("udp")
|
||||
|
||||
-- set a reasonable timeout value
|
||||
socket:set_timeout(5000)
|
||||
|
||||
local payload = strbuf.new()
|
||||
|
||||
-- for details about the UPnP message format, see http://upnp.org/resources/documents.asp
|
||||
payload = payload .. "M-SEARCH * HTTP/1.1\r\n"
|
||||
payload = payload .. "Host:239.255.255.250:1900\r\n"
|
||||
payload = payload .. "ST:upnp:rootdevice\r\n"
|
||||
payload = payload .. "Man:\"ssdp:discover\"\r\n"
|
||||
payload = payload .. "MX:3\r\n\r\n"
|
||||
|
||||
local status, err = socket:sendto("239.255.255.250", 1900, strbuf.dump(payload))
|
||||
if (not(status)) then return err end
|
||||
|
||||
local response, output
|
||||
local result = {}
|
||||
|
||||
while(true) do
|
||||
-- read in any response we might get
|
||||
status, response = socket:receive()
|
||||
if (not status) then break end
|
||||
|
||||
local status, _, _, peer_ip, _ = socket:get_info()
|
||||
|
||||
if target.ALLOW_NEW_TARGETS then
|
||||
target.add(peer_ip)
|
||||
end
|
||||
|
||||
output = process_response( response )
|
||||
output = { output }
|
||||
output.name = peer_ip
|
||||
table.insert( result, output )
|
||||
end
|
||||
socket:close()
|
||||
|
||||
table.sort(result, ipCompare)
|
||||
return stdnse.format_output(true, result)
|
||||
end
|
||||
|
||||
scanaction = function( host, port )
|
||||
|
||||
-- create the socket used for our connection
|
||||
local socket = nmap.new_socket()
|
||||
|
||||
-- set a reasonable timeout value
|
||||
socket:set_timeout(5000)
|
||||
|
||||
local payload = strbuf.new()
|
||||
|
||||
-- for details about the UPnP message format, see http://upnp.org/resources/documents.asp
|
||||
payload = payload .. "M-SEARCH * HTTP/1.1\r\n"
|
||||
payload = payload .. "Host:239.255.255.250:1900\r\n"
|
||||
payload = payload .. "ST:upnp:rootdevice\r\n"
|
||||
payload = payload .. "Man:\"ssdp:discover\"\r\n"
|
||||
payload = payload .. "MX:3\r\n\r\n"
|
||||
|
||||
local status, err = socket:connect(host, port, "udp" )
|
||||
if ( not(status) ) then return err end
|
||||
|
||||
status, err = socket:send( strbuf.dump(payload) )
|
||||
if ( not(status) ) then return err end
|
||||
|
||||
local response
|
||||
status, response = socket:receive()
|
||||
|
||||
if (not status) then
|
||||
socket:close()
|
||||
return response
|
||||
end
|
||||
|
||||
-- since we got something back, the port is definitely open
|
||||
nmap.set_port_state(host, port, "open")
|
||||
|
||||
return stdnse.format_output(true, process_response( response ))
|
||||
end
|
||||
|
||||
-- Function dispatch table
|
||||
local actions = {
|
||||
prerule = preaction,
|
||||
hostrule = scanaction,
|
||||
portrule = scanaction,
|
||||
}
|
||||
|
||||
function action (...) return actions[SCRIPT_TYPE](...) end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user