local coroutine = require "coroutine" local dns = require "dns" local nmap = require "nmap" local stdnse = require "stdnse" local tab = require "tab" local table = require "table" local target = require "target" description = [[ Enumerates various common service (SRV) records for a given domain name. The service records contain the hostname, port and priority of servers for a given service. The following services are enumerated by the script: - Active Directory Global Catalog - Exchange Autodiscovery - Kerberos KDC Service - Kerberos Passwd Change Service - LDAP Servers - SIP Servers - XMPP S2S - XMPP C2S ]] --- -- @usage -- nmap --script dns-srv-enum --script-args "dns-srv-enum.domain='example.com'" -- -- @output -- | dns-srv-enum: -- | Active Directory Global Catalog -- | service prio weight host -- | 3268/tcp 0 100 stodc01.example.com -- | Kerberos KDC Service -- | service prio weight host -- | 88/tcp 0 100 stodc01.example.com -- | 88/udp 0 100 stodc01.example.com -- | Kerberos Password Change Service -- | service prio weight host -- | 464/tcp 0 100 stodc01.example.com -- | 464/udp 0 100 stodc01.example.com -- | LDAP -- | service prio weight host -- | 389/tcp 0 100 stodc01.example.com -- | SIP -- | service prio weight host -- | 5060/udp 10 50 vclux2.example.com -- | 5070/udp 10 50 vcbxl2.example.com -- | 5060/tcp 10 50 vclux2.example.com -- | 5060/tcp 10 50 vcbxl2.example.com -- | XMPP server-to-server -- | service prio weight host -- | 5269/tcp 5 0 xmpp-server.l.example.com -- | 5269/tcp 20 0 alt2.xmpp-server.l.example.com -- | 5269/tcp 20 0 alt4.xmpp-server.l.example.com -- | 5269/tcp 20 0 alt3.xmpp-server.l.example.com -- |_ 5269/tcp 20 0 alt1.xmpp-server.l.example.com -- -- @args dns-srv-enum.domain string containing the domain to query -- @args dns-srv-enum.filter string containing the service to query -- (default: all) author = "Patrik Karlsson" license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"discovery", "safe"} local arg_domain = stdnse.get_script_args(SCRIPT_NAME .. ".domain") local arg_filter = stdnse.get_script_args(SCRIPT_NAME .. ".filter") prerule = function() return not(not(arg_domain)) end local function parseSvcList(services) local i = 1 return function() local svc = services[i] if ( svc ) then i=i + 1 else return end return svc.name, svc.query end end local function parseSrvResponse(resp) local i = 1 if ( resp.answers ) then table.sort(resp.answers, function(a, b) if ( a.SRV and b.SRV and a.SRV.prio and b.SRV.prio ) then return a.SRV.prio < b.SRV.prio end end ) end return function() if ( not(resp.answers) or 0 == #resp.answers ) then return end if ( not(resp.answers[i]) ) then return elseif ( resp.answers[i].SRV ) then local srv = resp.answers[i].SRV i = i + 1 return srv.target, srv.port, srv.prio, srv.weight end end end local function checkFilter(services) if ( not(arg_filter) or "" == arg_filter or "all" == arg_filter ) then return true end for name, queries in parseSvcList(services) do if ( name == arg_filter ) then return true end end return false end local function doQuery(name, queries, result) local condvar = nmap.condvar(result) local svc_result = tab.new(4) tab.addrow(svc_result, "service", "prio", "weight", "host") for _, query in ipairs(queries) do local fqdn = ("%s.%s"):format(query, arg_domain) local status, resp = dns.query(fqdn, { dtype="SRV", retAll=true, retPkt=true } ) for host, port, prio, weight in parseSrvResponse(resp) do if target.ALLOW_NEW_TARGETS then target.add(host) end local proto = query:sub(-3) tab.addrow(svc_result, ("%d/%s"):format(port, proto), prio, weight, host) end end if ( #svc_result ~= 1 ) then table.insert(result, { name = name, tab.dump(svc_result) }) end condvar "signal" end action = function(host) local services = { { name = "Active Directory Global Catalog", query = {"_gc._tcp"} }, { name = "Exchange Autodiscovery", query = {"_autodiscover._tcp"} }, { name = "Kerberos KDC Service", query = {"_kerberos._tcp", "_kerberos._udp"} }, { name = "Kerberos Password Change Service", query = {"_kpasswd._tcp", "_kpasswd._udp"} }, { name = "LDAP", query = {"_ldap._tcp"} }, { name = "SIP", query = {"_sip._udp", "_sip._tcp"} }, { name = "XMPP server-to-server", query = {"_xmpp-server._tcp"} }, { name = "XMPP client-to-server", query = {"_xmpp-client._tcp"} }, } if ( not(checkFilter(services)) ) then return stdnse.format_output(false, ("Invalid filter (%s) was supplied"):format(arg_filter)) end local threads, result = {}, {} for name, queries in parseSvcList(services) do if ( not(arg_filter) or 0 == #arg_filter or "all" == arg_filter or arg_filter == name ) then local co = stdnse.new_thread(doQuery, name, queries, result) threads[co] = true end end local condvar = nmap.condvar(result) repeat for t in pairs(threads) do if ( coroutine.status(t) == "dead" ) then threads[t] = nil end end if ( next(threads) ) then condvar "wait" end until( next(threads) == nil ) table.sort(result, function(a,b) return a.name < b.name end) return stdnse.format_output(true, result) end