mirror of
https://github.com/nmap/nmap.git
synced 2025-12-10 17:59:04 +00:00
o [NSE] Added a new library dnssd with supporting functions for DNS Service
Discovery. Moved multicast prerule from dns-service-discovery to a new script called broadcast-dns-service-discovery. [Patrik]
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o [NSE] Added a new library dnssd with supporting functions for DNS Service
|
||||||
|
Discovery. Moved multicast prerule from dns-service-discovery to a new
|
||||||
|
script called broadcast-dns-service-discovery. [Patrik]
|
||||||
|
|
||||||
o [NSE] Added the rmi-dumpregistry script, which shows the contents of
|
o [NSE] Added the rmi-dumpregistry script, which shows the contents of
|
||||||
Java RMI registry. [Martin Holst Swende]
|
Java RMI registry. [Martin Holst Swende]
|
||||||
|
|
||||||
|
|||||||
400
nselib/dnssd.lua
Normal file
400
nselib/dnssd.lua
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
--- Library for supporting DNS Service Discovery
|
||||||
|
--
|
||||||
|
-- The library supports
|
||||||
|
-- * Unicast and Multicast requests
|
||||||
|
-- * Decoding responses
|
||||||
|
-- * Running requests in parallell using Lua coroutines
|
||||||
|
--
|
||||||
|
-- The library contains the following classes
|
||||||
|
-- * <code>Comm</code>
|
||||||
|
-- ** A class with static functions that handle communication using the dns library
|
||||||
|
-- * <code>Helper</code>
|
||||||
|
-- ** The helper class wraps the <code>Comm</code> class using functions with a more descriptive name.
|
||||||
|
-- ** The purpose of this class is to give developers easy access to some of the common DNS-SD tasks.
|
||||||
|
-- * <code>Util</code>
|
||||||
|
-- ** The <code>Util</code> class contains a number of static functions mainly used to convert data.
|
||||||
|
--
|
||||||
|
-- The following code snipplet queries all mDNS resolvers on the network for a
|
||||||
|
-- full list of their supported services and returns the formated output:
|
||||||
|
-- <code>
|
||||||
|
-- local helper = dnssd.Helper:new( )
|
||||||
|
-- helper:setMulticast(true)
|
||||||
|
-- return stdnse.format_output(helper:queryServices())
|
||||||
|
-- </code>
|
||||||
|
--
|
||||||
|
-- This next snipplet queries a specific host for the same information:
|
||||||
|
-- <code>
|
||||||
|
-- local helper = dnssd.Helper:new( host, port )
|
||||||
|
-- return stdnse.format_output(helper:queryServices())
|
||||||
|
-- </code>
|
||||||
|
--
|
||||||
|
-- In order to query for a specific service a string or table with service
|
||||||
|
-- names can be passed to the <code>Helper.queryServices</code> method.
|
||||||
|
--
|
||||||
|
-- @args dnssd.services string or table containing services to query
|
||||||
|
--
|
||||||
|
-- @author Patrik Karlsson <patrik@cqure.net>
|
||||||
|
-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
|
||||||
|
--
|
||||||
|
|
||||||
|
module(... or "dnssd", package.seeall)
|
||||||
|
|
||||||
|
require 'dns'
|
||||||
|
require 'target'
|
||||||
|
|
||||||
|
Util = {
|
||||||
|
|
||||||
|
--- 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
|
||||||
|
ipToNumber = function(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
|
||||||
|
ipCompare = function(a, b)
|
||||||
|
local ip_a = Util.ipToNumber(a.name) or 0
|
||||||
|
local ip_b = Util.ipToNumber(b.name) or 0
|
||||||
|
|
||||||
|
if ( tonumber(ip_a) < tonumber(ip_b) ) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Function used to compare discovered DNS services so they can be sorted
|
||||||
|
--
|
||||||
|
-- @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
|
||||||
|
serviceCompare = function(a, b)
|
||||||
|
-- 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
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
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 multiple responses from <code>dns.query</code>
|
||||||
|
-- @return services table containing the service name as a key and all host addresses as value
|
||||||
|
createSvcHostTbl = function( response )
|
||||||
|
local services = {}
|
||||||
|
-- Create unique table of services
|
||||||
|
for _, r in ipairs( response ) do
|
||||||
|
-- do we really have mutliple responses?
|
||||||
|
if ( not(r.output) ) then return end
|
||||||
|
for _, svc in ipairs(r.output ) do
|
||||||
|
services[svc] = services[svc] or {}
|
||||||
|
table.insert(services[svc], r.peer)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return services
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Creates a unique list of services
|
||||||
|
--
|
||||||
|
-- @param response containing a single or multiple responses from
|
||||||
|
-- <code>dns.query</code>
|
||||||
|
-- @return array of strings containing service names
|
||||||
|
getUniqueServices = function( response )
|
||||||
|
local services = {}
|
||||||
|
|
||||||
|
for _, r in ipairs(response) do
|
||||||
|
if ( r.output ) then
|
||||||
|
for _, svc in ipairs(r.output) do services[svc] = true end
|
||||||
|
else
|
||||||
|
services[r] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return services
|
||||||
|
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
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Comm = {
|
||||||
|
|
||||||
|
--- Gets a record from both the Answer and Additional section
|
||||||
|
--
|
||||||
|
-- @param dtype DNS resource record type.
|
||||||
|
-- @param response Decoded DNS response.
|
||||||
|
-- @param retAll If true, return all entries, not just the first.
|
||||||
|
-- @return True if one or more answers of the required type were found - otherwise false.
|
||||||
|
-- @return Answer according to the answer fetcher for <code>dtype</code> or an Error message.
|
||||||
|
getRecordType = function( dtype, response, retAll )
|
||||||
|
|
||||||
|
local result = {}
|
||||||
|
local status1, answers = dns.findNiceAnswer( dtype, response, retAll )
|
||||||
|
|
||||||
|
if status1 then
|
||||||
|
if retAll then
|
||||||
|
for _, v in ipairs(answers) do
|
||||||
|
table.insert(result, string.format("%s", v) )
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return true, answers
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local status2, answers = dns.findNiceAdditional( dtype, response, retAll )
|
||||||
|
|
||||||
|
if status2 then
|
||||||
|
if retAll then
|
||||||
|
for _, v in ipairs(answers) do
|
||||||
|
table.insert(result, v)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return true, answers
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not status1 and not status2 then
|
||||||
|
return false, answers
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, result
|
||||||
|
|
||||||
|
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
|
||||||
|
queryService = function( 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,
|
||||||
|
|
||||||
|
--- Decodes a record received from the <code>queryService</code> function
|
||||||
|
--
|
||||||
|
-- @param response as returned by <code>queryService</code>
|
||||||
|
-- @param result table into which the decoded output should be stored
|
||||||
|
decodeRecords = function( response, result )
|
||||||
|
local service, deviceinfo = {}, {}
|
||||||
|
local txt = {}
|
||||||
|
local ipv6, srv, address, port, proto
|
||||||
|
|
||||||
|
local record = ( #response.questions > 0 and response.questions[1].dname ) and response.questions[1].dname or ""
|
||||||
|
|
||||||
|
local status, ip = Comm.getRecordType( dns.types.A, response, false )
|
||||||
|
if status then address = ip end
|
||||||
|
|
||||||
|
status, ipv6 = Comm.getRecordType( dns.types.AAAA, response, false )
|
||||||
|
if status then address = address .. " " .. ipv6 end
|
||||||
|
|
||||||
|
status, txt = Comm.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 = Comm.getRecordType( dns.types.SRV, response, false )
|
||||||
|
if status then
|
||||||
|
local srvparams = stdnse.strsplit( ":", srv )
|
||||||
|
|
||||||
|
if #srvparams > 3 then
|
||||||
|
port = srvparams[3]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
table.insert( result, service )
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Query the mDNS resolvers for a list of their services
|
||||||
|
--
|
||||||
|
-- @param host table as received by the action function
|
||||||
|
-- @param port number specifying the port to connect to
|
||||||
|
-- @param multiple receive multiple responses (multicast)
|
||||||
|
-- @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:
|
||||||
|
-- "No Such Name", "No Servers", "No Answers",
|
||||||
|
-- "Unable to handle response"
|
||||||
|
queryAllServices = function( host, port, multiple )
|
||||||
|
local sendCount, timeout = 1, 2000
|
||||||
|
if ( multiple ) then
|
||||||
|
sendCount, timeout = 2, 5000
|
||||||
|
end
|
||||||
|
return dns.query( "_services._dns-sd._udp.local", { port = port, host = ( host.ip or host ), dtype="PTR", retAll=true, multiple=multiple, sendCount=sendCount, timeout=timeout } )
|
||||||
|
end,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Helper = {
|
||||||
|
|
||||||
|
--- Creates a new helper instance
|
||||||
|
--
|
||||||
|
-- @param host string containing the host name or ip
|
||||||
|
-- @param port number containing the port to connect to
|
||||||
|
-- @return o a new instance of Helper
|
||||||
|
new = function( self, host, port )
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
o.host = host
|
||||||
|
o.port = port
|
||||||
|
o.mcast = false
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
|
||||||
|
--- Instructs the helper to use unconnected sockets supporting multicast
|
||||||
|
--
|
||||||
|
-- @param mcast boolean true if multicast is to be used, false otherwise
|
||||||
|
setMulticast = function( self, mcast )
|
||||||
|
assert( type(mcast)=="boolean", "mcast has to be either true or false")
|
||||||
|
self.mcast = mcast
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Performs a DNS-SD query against a host
|
||||||
|
--
|
||||||
|
-- @param host table as received by the action function
|
||||||
|
-- @param port number specifying the port to connect to
|
||||||
|
-- @param service string or table with the service(s) to query eg.
|
||||||
|
-- _ssh._tcp.local, _afpovertcp._tcp.local
|
||||||
|
-- if nil defaults to _services._dns-sd._udp.local (all)
|
||||||
|
-- @param mcast boolean true if a multicast query is to be done
|
||||||
|
-- @return status true on success, false on failure
|
||||||
|
-- @return response table suitable for <code>stdnse.format_output</code>
|
||||||
|
queryServices = function( self, service )
|
||||||
|
local result = {}
|
||||||
|
local status, response
|
||||||
|
local mcast = self.mcast
|
||||||
|
local port = self.port or 5353
|
||||||
|
local host = mcast and "224.0.0.251" or self.host
|
||||||
|
local service = service or stdnse.get_script_args('dnssd.services')
|
||||||
|
|
||||||
|
if ( not(service) ) then
|
||||||
|
status, response = Comm.queryAllServices( host, port, mcast )
|
||||||
|
if ( not(status) ) then return status, response end
|
||||||
|
else
|
||||||
|
if ( 'string' == type(service) ) then
|
||||||
|
response = { service }
|
||||||
|
elseif ( 'table' == type(service) ) then
|
||||||
|
response = service
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
response = Util.getUniqueServices(response)
|
||||||
|
|
||||||
|
local svcresponse = {}
|
||||||
|
local condvar = nmap.condvar( svcresponse )
|
||||||
|
local threads = {}
|
||||||
|
|
||||||
|
for svc in pairs(response) do
|
||||||
|
local co = stdnse.new_thread( Comm.queryService, (host.ip or host), port, svc, mcast, svcresponse )
|
||||||
|
threads[co] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Wait for all threads to finish running
|
||||||
|
while Util.threadCount(threads)>0 do condvar("wait") end
|
||||||
|
|
||||||
|
local ipsvctbl = {}
|
||||||
|
if ( mcast ) then
|
||||||
|
-- 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 {}
|
||||||
|
Comm.decodeRecords( r.output, ipsvctbl[r.peer] )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Process all records that were returned
|
||||||
|
for svcname, response in pairs(svcresponse) do
|
||||||
|
Comm.decodeRecords( response, result )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ( mcast ) then
|
||||||
|
-- Restructure and build our output table
|
||||||
|
for ip, svctbl in pairs( ipsvctbl ) do
|
||||||
|
table.sort(svctbl, Util.serviceCompare)
|
||||||
|
svctbl.name = ip
|
||||||
|
if target.ALLOW_NEW_TARGETS then target.add(ip) end
|
||||||
|
table.insert( result, svctbl )
|
||||||
|
end
|
||||||
|
table.sort( result, Util.ipCompare )
|
||||||
|
else
|
||||||
|
-- sort the tables per port
|
||||||
|
table.sort( result, Util.serviceCompare )
|
||||||
|
end
|
||||||
|
return true, result
|
||||||
|
end,
|
||||||
|
|
||||||
|
}
|
||||||
58
scripts/broadcast-dns-service-discovery.nse
Normal file
58
scripts/broadcast-dns-service-discovery.nse
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
description=[[
|
||||||
|
Attempts to discover a hosts services using the DNS Service Discovery protocol.
|
||||||
|
It does so by sending a multicast query and collects responses from all
|
||||||
|
responding hosts.
|
||||||
|
|
||||||
|
The script first sends a query for _services._dns-sd._udp.local to get a
|
||||||
|
list of services. It then sends a followup query for each one to try to
|
||||||
|
get more information.
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap --script=broadcast-dns-service-discovery -p 5353 <target>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- | broadcast-dns-service-discovery:
|
||||||
|
-- | 1.2.3.1
|
||||||
|
-- | _ssh._tcp.local
|
||||||
|
-- | _http._tcp.local
|
||||||
|
-- | 1.2.3.50
|
||||||
|
-- | 22/tcp ssh
|
||||||
|
-- | org.freedesktop.Avahi.cookie=2292090182
|
||||||
|
-- | Address=1.2.3.50
|
||||||
|
-- | 80/tcp http
|
||||||
|
-- | path=/admin
|
||||||
|
-- | org.freedesktop.Avahi.cookie=2292090182
|
||||||
|
-- | path=/
|
||||||
|
-- | org.freedesktop.Avahi.cookie=2292090182
|
||||||
|
-- | path=/pim
|
||||||
|
-- | org.freedesktop.Avahi.cookie=2292090182
|
||||||
|
-- | Address=1.2.3.50
|
||||||
|
-- | 1.2.3.116
|
||||||
|
-- | 80/tcp http
|
||||||
|
-- |_ Address=1.2.3.116
|
||||||
|
|
||||||
|
|
||||||
|
-- Version 0.1
|
||||||
|
-- Created 10/29/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
||||||
|
|
||||||
|
author = "Patrik Karlsson"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"default", "discovery", "safe"}
|
||||||
|
|
||||||
|
require 'shortport'
|
||||||
|
require 'dnssd'
|
||||||
|
|
||||||
|
prerule = function() return true end
|
||||||
|
|
||||||
|
action = function()
|
||||||
|
local helper = dnssd.Helper:new( )
|
||||||
|
helper:setMulticast(true)
|
||||||
|
|
||||||
|
local status, result = helper:queryServices()
|
||||||
|
if ( status ) then
|
||||||
|
return stdnse.format_output(true, result)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -33,317 +33,32 @@ get more information.
|
|||||||
-- |_ Address=192.168.0.2 fe80:0:0:0:223:6cff:1234:5678
|
-- |_ Address=192.168.0.2 fe80:0:0:0:223:6cff:1234:5678
|
||||||
|
|
||||||
|
|
||||||
-- Version 0.6
|
-- Version 0.7
|
||||||
-- Created 01/06/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
|
-- 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 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 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/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/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>
|
-- Revised 10/10/2010 - v0.6 - multicast queries are now used in parallel to collect service information <patrik@cqure.net>
|
||||||
|
-- Revised 10/29/2010 - v0.7 - factored out most of the code to dnssd library
|
||||||
|
|
||||||
author = "Patrik Karlsson"
|
author = "Patrik Karlsson"
|
||||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
categories = {"default", "discovery", "safe"}
|
categories = {"default", "discovery", "safe"}
|
||||||
|
|
||||||
require 'shortport'
|
require 'shortport'
|
||||||
require 'dns'
|
require 'dnssd'
|
||||||
require 'target'
|
|
||||||
|
|
||||||
portrule = shortport.portnumber(5353, "udp")
|
portrule = shortport.portnumber(5353, "udp")
|
||||||
prerule = function() return true end
|
|
||||||
|
|
||||||
--- Gets a record from both the Answer and Additional section
|
action = function(host, port)
|
||||||
--
|
local helper = dnssd.Helper:new( host, port )
|
||||||
-- @param dtype DNS resource record type.
|
local status, result = helper:queryServices()
|
||||||
-- @param response Decoded DNS response.
|
|
||||||
-- @param retAll If true, return all entries, not just the first.
|
|
||||||
-- @return True if one or more answers of the required type were found - otherwise false.
|
|
||||||
-- @return Answer according to the answer fetcher for <code>dtype</code> or an Error message.
|
|
||||||
function getRecordType( dtype, response, retAll )
|
|
||||||
|
|
||||||
local result = {}
|
|
||||||
local status1, answers = dns.findNiceAnswer( dtype, response, retAll )
|
|
||||||
|
|
||||||
if status1 then
|
|
||||||
if retAll then
|
|
||||||
for _, v in ipairs(answers) do
|
|
||||||
table.insert(result, string.format("%s", v) )
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return true, answers
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local status2, answers = dns.findNiceAdditional( dtype, response, retAll )
|
|
||||||
|
|
||||||
if status2 then
|
|
||||||
if retAll then
|
|
||||||
for _, v in ipairs(answers) do
|
|
||||||
table.insert(result, v)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return true, answers
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not status1 and not status2 then
|
|
||||||
return false, answers
|
|
||||||
end
|
|
||||||
|
|
||||||
return true, result
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Function used to compare discovered DNS services so they can be sorted
|
|
||||||
--
|
|
||||||
-- @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 serviceCompare(a, b)
|
|
||||||
-- 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
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
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) or 0
|
|
||||||
local ip_b = ipToNumber(b.name) or 0
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
local record = ( #response.questions > 0 and response.questions[1].dname ) and response.questions[1].dname or ""
|
|
||||||
|
|
||||||
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 )
|
|
||||||
|
|
||||||
if #srvparams > 3 then
|
|
||||||
port = srvparams[3]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
table.insert( result, service )
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
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 )
|
|
||||||
|
|
||||||
|
if ( status ) then
|
||||||
-- set port to open
|
-- set port to open
|
||||||
nmap.set_port_state(host, port, "open")
|
nmap.set_port_state(host, port, "open")
|
||||||
|
return stdnse.format_output(true, result)
|
||||||
return stdnse.format_output(true, result )
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Function dispatch table
|
|
||||||
local actions = {
|
|
||||||
prerule = preaction,
|
|
||||||
hostrule = scanaction,
|
|
||||||
portrule = scanaction,
|
|
||||||
}
|
|
||||||
|
|
||||||
function action (...) return actions[SCRIPT_TYPE](...) end
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Entry { filename = "asn-query.nse", categories = { "discovery", "external", "saf
|
|||||||
Entry { filename = "auth-owners.nse", categories = { "default", "safe", } }
|
Entry { filename = "auth-owners.nse", categories = { "default", "safe", } }
|
||||||
Entry { filename = "auth-spoof.nse", categories = { "malware", "safe", } }
|
Entry { filename = "auth-spoof.nse", categories = { "malware", "safe", } }
|
||||||
Entry { filename = "banner.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "banner.nse", categories = { "discovery", "safe", } }
|
||||||
|
Entry { filename = "broadcast-dns-service-discovery.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "citrix-brute-xml.nse", categories = { "auth", "intrusive", } }
|
Entry { filename = "citrix-brute-xml.nse", categories = { "auth", "intrusive", } }
|
||||||
Entry { filename = "citrix-enum-apps-xml.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "citrix-enum-apps-xml.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "citrix-enum-apps.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "citrix-enum-apps.nse", categories = { "discovery", "safe", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user