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

Use SOA request to get domain name in dns-zone-transfer. Fixes #3014

This commit is contained in:
dmiller
2025-07-14 19:17:09 +00:00
parent ca0324fc2f
commit 6637d76846
2 changed files with 33 additions and 76 deletions

View File

@@ -1,6 +1,7 @@
local dns = require "dns"
local ipOps = require "ipOps"
local listop = require "listop"
local math = require "math"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
@@ -105,12 +106,12 @@ prerule = function()
)
if not dns_opts.domain then
stdnse.debug3("Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.", SCRIPT_NAME, SCRIPT_TYPE)
stdnse.verbose1("Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.", SCRIPT_NAME, SCRIPT_TYPE)
return false
end
if not dns_opts.server then
stdnse.debug3("Skipping '%s' %s, 'dnszonetransfer.server' argument is missing.", SCRIPT_NAME, SCRIPT_TYPE)
stdnse.verbose1("Skipping '%s' %s, 'dnszonetransfer.server' argument is missing.", SCRIPT_NAME, SCRIPT_TYPE)
return false
end
@@ -158,30 +159,6 @@ local typetab = { 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR',
[32768]='TA', [32769]='DLV',
}
--- Whitelist of TLDs. Only way to reliably determine the root of a domain
--@class table
--@name tld
local tld = {
'aero', 'asia', 'biz', 'cat', 'com', 'coop', 'info', 'jobs', 'mobi', 'museum',
'name', 'net', 'org', 'pro', 'tel', 'travel', 'gov', 'edu', 'mil', 'int',
'ac','ad','ae','af','ag','ai','al','am','an','ao','aq','ar','as','at','au','aw',
'ax','az','ba','bb','bd','be','bf','bg','bh','bi','bj','bm','bn','bo','br','bs',
'bt','bv','bw','by','bz','ca','cc','cd','cf','cg','ch','ci','ck','cl','cm','cn',
'co','cr','cu','cv','cx','cy','cz','de','dj','dk','dm','do','dz','ec','ee','eg',
'eh','er','es','et','eu','fi','fj','fk','fm','fo','fr','ga','gb','gd','ge','gf',
'gg','gh','gi','gl','gm','gn','gp','gq','gr','gs','gt','gu','gw','gy','hk','hm',
'hn','hr','ht','hu','id','ie','il','im','in','io','iq','ir','is','it','je','jm',
'jo','jp','ke','kg','kh','ki','km','kn','kp','kr','kw','ky','kz','la','lb','lc',
'li','lk','lr','ls','lt','lu','lv','ly','ma','mc','md','me','mg','mh','mk','ml',
'mm','mn','mo','mp','mq','mr','ms','mt','mu','mv','mw','mx','my','mz','na','nc',
'ne','nf','ng','ni','nl','no','np','nr','nu','nz','om','pa','pe','pf','pg','ph',
'pk','pl','pm','pn','pr','ps','pt','pw','py','qa','re','ro','rs','ru','rw','sa',
'sb','sc','sd','se','sg','sh','si','sj','sk','sl','sm','sn','so','sr','st','su',
'sv','sy','sz','tc','td','tf','tg','th','tj','tk','tl','tm','tn','to','tp','tr',
'tt','tv','tw','tz','ua','ug','uk','um','us','uy','uz','va','vc','ve','vg','vi',
'vn','vu','wf','ws','ye','yt','yu','za','zm','zw'
}
--- Convert two bytes into a 16bit number.
--@param data String of data.
--@param idx Index in the string (first of two consecutive bytes).
@@ -190,16 +167,6 @@ function bto16(data, idx)
return (">I2"):unpack(data, idx)
end
--- Check if domain name element is a tld
--@param elm Domain name element to check.
--@return boolean
function valid_tld(elm)
for i,v in ipairs(tld) do
if elm == v then return true end
end
return false
end
--- Parse an RFC 1035 domain name.
--@param data String of data.
--@param offset Offset in the string to read the domain name.
@@ -209,38 +176,6 @@ function parse_domain(data, offset)
return offset, string.format("%s.", domain)
end
--- Build RFC 1035 root domain name from the name of the DNS server
-- (e.g ns1.website.com.ar -> \007website\003com\002ar\000).
--@param host The host.
function build_domain(host)
local names, buf, x
local abs_name, i, tmp
buf = strbuf.new()
abs_name = {}
names = stringaux.strsplit('%.', host)
if names == nil then names = {host} end
-- try to determine root of domain name
for i, x in ipairs(listop.reverse(names)) do
table.insert(abs_name, x)
if not valid_tld(x) then break end
end
i = 1
abs_name = listop.reverse(abs_name)
-- prepend each element with its length
while i <= #abs_name do
buf = buf .. string.char(#abs_name[i]) .. abs_name[i]
i = i + 1
end
buf = buf .. '\000'
return strbuf.dump(buf)
end
local function parse_num_domain(data, offset)
local number, domain
number = bto16(data, offset)
@@ -720,19 +655,40 @@ action = function(host, port)
string.format("'%s' script needs a dnszonetransfer.domain argument.",
SCRIPT_TYPE))
end
if not dns_opts.port then
dns_opts.port = 53
if not host then
host = dns_opts.server
end
local proto = "tcp"
if not port then
port = dns_opts.port or 53
else
proto = port.protocol
end
local soc = nmap.new_socket()
local catch = function() soc:close() end
local try = nmap.new_try(catch)
soc:set_timeout(4000)
try(soc:connect(dns_opts.server, dns_opts.port))
try(soc:connect(host, port))
-- To find the domain portion, we issue a SOA request.
local dnspacket = dns.newPacket()
dns.addQuestion(dnspacket, dns_opts.domain:lower(), dns.types.SOA, dns.CLASS.IN)
dnspacket.id = math.random(0xffff)
local response = try(dns.sendPackets(dns.encode(dnspacket), host, port, 4000, 1, nil, proto))
local soa_pkt = dns.decode(response[1].data)
local name
if #soa_pkt.answers > 0 then
-- either it's a domain and the domain is in answer,
name = soa_pkt.answers[1].dname
elseif #soa_pkt.auth > 0 then
-- or it's a hostname and the domain is in authority.
name = soa_pkt.auth[1].dname
end
local req_id = '\222\173'
local offset = 1
local name = build_domain(string.lower(dns_opts.domain))
name = dns.encodeFQDN(name)
local pkt_len = #name + 16
-- build axfr request