1
0
mirror of https://github.com/nmap/nmap.git synced 2026-01-06 06:29:03 +00:00

Fix whitespace in dns-zone-transfer.nse.

This commit is contained in:
david
2010-09-30 17:05:12 +00:00
parent e7fc9c4c5f
commit 60636135a2

View File

@@ -75,16 +75,16 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {'default', 'intrusive', 'discovery'}
prerule = function() return true end
portrule = shortport.portnumber(53, 'tcp')
portrule = shortport.portnumber(53, 'tcp')
--- DNS query and response types.
--@class table
--@name typetab
local typetab = { 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR',
local typetab = { 'A', 'NS', 'MD', 'MF', 'CNAME', 'SOA', 'MB', 'MG', 'MR',
'NULL', 'WKS', 'PTR', 'HINFO', 'MINFO', 'MX', 'TXT', 'RP', 'AFSDB', 'X25',
'ISDN', 'RT', 'NSAP', 'NSAP-PTR', 'SIG', 'KEY', 'PX', 'GPOS', 'AAAA', 'LOC',
'NXT', 'EID', 'NIMLOC', 'SRV', 'ATMA', 'NAPTR', 'KX', 'CERT', 'A6', 'DNAME',
'SINK', 'OPT', [250]='TSIG', [251]='IXFR', [252]='AXFR', [253]='MAILB',
'SINK', 'OPT', [250]='TSIG', [251]='IXFR', [252]='AXFR', [253]='MAILB',
[254]='MAILA', [255]='ANY', [256]='ZXFR'
}
@@ -109,217 +109,217 @@ local tld = {
'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'
'vn','vu','wf','ws','ye','yt','yu','za','zm','zw'
}
--- Convert two bytes into a 16bit number.
--- Convert two bytes into a 16bit number.
--@param data String of data.
--@param idx Index in the string (first of two consecutive bytes).
--@return 16 bit number represented by the two bytes.
function bto16(data, idx)
local b1 = string.byte(data, idx)
local b2 = string.byte(data, idx+1)
-- (b2 & 0xff) | ((b1 & 0xff) << 8)
return bit.bor(bit.band(b2, 255), bit.lshift(bit.band(b1, 255), 8))
local b1 = string.byte(data, idx)
local b2 = string.byte(data, idx+1)
-- (b2 & 0xff) | ((b1 & 0xff) << 8)
return bit.bor(bit.band(b2, 255), bit.lshift(bit.band(b1, 255), 8))
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
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.
function parse_domain(data, offset)
local offset, domain = dns.decStr(data, offset)
domain = domain or "<parse error>"
return offset, domain
end
local offset, domain = dns.decStr(data, offset)
domain = domain or "<parse error>"
return offset, 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
local names, buf, x
local abs_name, i, tmp
buf = strbuf.new()
abs_name = {}
buf = strbuf.new()
abs_name = {}
names = stdnse.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)
names = stdnse.strsplit('%.', host)
if names == nil then names = {host} end
-- prepend each element with its length
while i <= table.getn(abs_name) do
buf = buf .. string.char(string.len(abs_name[i])) .. abs_name[i]
i = i + 1
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
buf = buf .. '\000'
return strbuf.dump(buf)
i = 1
abs_name = listop.reverse(abs_name)
-- prepend each element with its length
while i <= table.getn(abs_name) do
buf = buf .. string.char(string.len(abs_name[i])) .. abs_name[i]
i = i + 1
end
buf = buf .. '\000'
return strbuf.dump(buf)
end
--- Retrieve type specific data (rdata) from dns packets
function get_rdata(data, offset, ttype)
local field, info, i
local field, info, i
info = strbuf.new()
info = info .. ''
info = strbuf.new()
info = info .. ''
if typetab[ttype] == nil then
return offset, ''
if typetab[ttype] == nil then
return offset, ''
elseif typetab[ttype] == 'SOA' then
-- name server
offset, field = parse_domain(data, offset)
info = info .. field;
-- mail box
offset, field = parse_domain(data, offset)
info = info .. field;
-- ignore other values
offset = offset + 20
elseif typetab[ttype] == 'SOA' then
-- name server
offset, field = parse_domain(data, offset)
info = info .. field;
-- mail box
offset, field = parse_domain(data, offset)
info = info .. field;
-- ignore other values
offset = offset + 20
elseif typetab[ttype] == 'MX' then
-- mail server
offset = offset + 2
offset, field = parse_domain(data, offset)
info = info .. field
elseif typetab[ttype] == 'MX' then
-- mail server
offset = offset + 2
offset, field = parse_domain(data, offset)
info = info .. field
elseif typetab[ttype] == 'A' then
-- ip address
info = info ..
string.byte(data, offset) .. '.' ..
string.byte(data, offset+1) .. '.' ..
string.byte(data, offset+2) .. '.' ..
string.byte(data, offset+3)
offset = offset + 4
elseif typetab[ttype] == 'A' then
-- ip address
info = info ..
string.byte(data, offset) .. '.' ..
string.byte(data, offset+1) .. '.' ..
string.byte(data, offset+2) .. '.' ..
string.byte(data, offset+3)
offset = offset + 4
elseif typetab[ttype] == 'PTR' or
elseif typetab[ttype] == 'PTR' or
typetab[ttype] == 'NS' then
-- domain/domain server name
offset, field = parse_domain(data, offset)
info = info .. field;
end
-- domain/domain server name
offset, field = parse_domain(data, offset)
info = info .. field;
end
return offset, strbuf.dump(info, ' ')
return offset, strbuf.dump(info, ' ')
end
--- Get a single answer record from the current offset
function get_answer_record(table, data, offset)
local line, rdlen, ttype
-- answer domain
offset, line = parse_domain(data, offset)
tab.add(table, 1, line)
local line, rdlen, ttype
-- answer record type
ttype = bto16(data, offset)
if not(typetab[ttype] == nil) then
tab.add(table, 2, typetab[ttype])
end
-- answer domain
offset, line = parse_domain(data, offset)
tab.add(table, 1, line)
-- length of type specific data
rdlen = bto16(data, offset+8)
-- answer record type
ttype = bto16(data, offset)
if not(typetab[ttype] == nil) then
tab.add(table, 2, typetab[ttype])
end
-- extra data, ignore ttl and class
offset, line = get_rdata(data, offset+10, ttype)
if(line == '') then
offset = offset + rdlen
else
tab.add(table, 3, line)
end
-- length of type specific data
rdlen = bto16(data, offset+8)
return offset, tab
-- extra data, ignore ttl and class
offset, line = get_rdata(data, offset+10, ttype)
if(line == '') then
offset = offset + rdlen
else
tab.add(table, 3, line)
end
return offset, tab
end
function parse_records(number, data, table, offset)
while number > 0 do
tab.nextrow(table)
offset = get_answer_record(table, data, offset)
number = number - 1
end
return offset
while number > 0 do
tab.nextrow(table)
offset = get_answer_record(table, data, offset)
number = number - 1
end
return offset
end
-- An iterator that breaks up a concatentation of responses. In DNS over TCP,
-- each response is prefixed by a two-byte length (RFC 1035 section 4.2.2).
-- Responses returned by this iterator include the two-byte length prefix.
function responses_iter(data)
local offset = 1
local offset = 1
return function()
local length, remaining, response
return function()
local length, remaining, response
remaining = string.len(data) - offset + 1
if remaining == 0 then
return nil
end
assert(remaining >= 14 + 2)
length = bto16(data, offset)
assert(length <= remaining)
-- Skip over the length field.
offset = offset + 2
response = string.sub(data, offset, offset + length - 1)
offset = offset + length
return response
end
remaining = string.len(data) - offset + 1
if remaining == 0 then
return nil
end
assert(remaining >= 14 + 2)
length = bto16(data, offset)
assert(length <= remaining)
-- Skip over the length field.
offset = offset + 2
response = string.sub(data, offset, offset + length - 1)
offset = offset + length
return response
end
end
function dump_zone_info(table, data)
local answers, line, offset
local questions, auth_answers, add_answers
offset = 1
-- number of available records
questions = bto16(data, offset+4)
answers = bto16(data, offset+6)
auth_answers = bto16(data, offset+8)
add_answers = bto16(data, offset+10)
local answers, line, offset
local questions, auth_answers, add_answers
-- move to beginning of first section
offset = offset + 12
offset = 1
-- number of available records
questions = bto16(data, offset+4)
answers = bto16(data, offset+6)
auth_answers = bto16(data, offset+8)
add_answers = bto16(data, offset+10)
if questions > 1 then
return 'More then 1 question record, something has gone wrong'
end
-- move to beginning of first section
offset = offset + 12
if answers == 0 then
return 'transfer successful but no records'
end
if questions > 1 then
return 'More then 1 question record, something has gone wrong'
end
-- skip over the question section, we don't need it
if questions == 1 then
offset, line = parse_domain(data, offset)
offset = offset + 4
end
-- parse all available resource records
offset = parse_records(answers, data, table, offset)
offset = parse_records(auth_answers, data, table, offset)
offset = parse_records(add_answers, data, table, offset)
return offset
if answers == 0 then
return 'transfer successful but no records'
end
-- skip over the question section, we don't need it
if questions == 1 then
offset, line = parse_domain(data, offset)
offset = offset + 4
end
-- parse all available resource records
offset = parse_records(answers, data, table, offset)
offset = parse_records(auth_answers, data, table, offset)
offset = parse_records(add_answers, data, table, offset)
return offset
end
action = function(host, port)
local soc, status, data
local catch = function() soc:close() end
local try = nmap.new_try(catch)
local args = nmap.registry.args
local domain, dns_server, dns_port = stdnse.get_script_args(
@@ -333,78 +333,77 @@ action = function(host, port)
-- script running at the Script Pre-scanning phase.
if SCRIPT_TYPE == "prerule" then
if not domain then
stdnse.print_debug(3,
"Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.",
SCRIPT_NAME, SCRIPT_TYPE)
return
end
if not dns_server then
stdnse.print_debug(3,
"Skipping '%s' %s, 'dnszonetransfer.server' argument is missing.",
SCRIPT_NAME, SCRIPT_TYPE)
return
end
-- script running at the Script Scan phase.
elseif SCRIPT_TYPE == "portrule" then
if not domain then
if host.targetname then
domain = host.targetname
elseif host.name ~= "" then
domain = host.name
else
-- can't do anything without a hostname
return stdnse.format_output(false,
string.format("'%s' script needs a dnszonetransfer.domain argument.",
SCRIPT_TYPE))
if not domain then
stdnse.print_debug(3,
"Skipping '%s' %s, 'dnszonetransfer.domain' argument is missing.",
SCRIPT_NAME, SCRIPT_TYPE)
return
end
end
dns_server = host.ip
dns_port = port.number
if not dns_server then
stdnse.print_debug(3,
"Skipping '%s' %s, 'dnszonetransfer.server' argument is missing.",
SCRIPT_NAME, SCRIPT_TYPE)
return
end
-- script running at the Script Scan phase.
elseif SCRIPT_TYPE == "portrule" then
if not domain then
if host.targetname then
domain = host.targetname
elseif host.name ~= "" then
domain = host.name
else
-- can't do anything without a hostname
return stdnse.format_output(false,
string.format("'%s' script needs a dnszonetransfer.domain argument.", SCRIPT_TYPE))
end
end
dns_server = host.ip
dns_port = port.number
end
assert(domain)
assert(domain)
soc = nmap.new_socket()
soc:set_timeout(4000)
try(soc:connect(dns_server, dns_port))
soc = nmap.new_socket()
soc:set_timeout(4000)
try(soc:connect(dns_server, dns_port))
local req_id = '\222\173'
local table = tab.new(3)
local offset = 1
local name = build_domain(string.lower(domain))
local pkt_len = string.len(name) + 16
local req_id = '\222\173'
local table = tab.new(3)
local offset = 1
local name = build_domain(string.lower(domain))
local pkt_len = string.len(name) + 16
-- build axfr request
local buf = strbuf.new()
buf = buf .. '\000' .. string.char(pkt_len) .. req_id
buf = buf .. '\000\000\000\001\000\000\000\000\000\000'
buf = buf .. name .. '\000\252\000\001'
try(soc:send(strbuf.dump(buf)))
-- build axfr request
local buf = strbuf.new()
buf = buf .. '\000' .. string.char(pkt_len) .. req_id
buf = buf .. '\000\000\000\001\000\000\000\000\000\000'
buf = buf .. name .. '\000\252\000\001'
try(soc:send(strbuf.dump(buf)))
-- read all data returned. Common to have
-- multiple packets from a single request
local response = strbuf.new()
while true do
status, data = soc:receive_bytes(1)
if not status then break end
response = response .. data
end
-- read all data returned. Common to have
-- multiple packets from a single request
local response = strbuf.new()
while true do
status, data = soc:receive_bytes(1)
if not status then break end
response = response .. data
end
local response_str = strbuf.dump(response)
local length = string.len(response_str)
local response_str = strbuf.dump(response)
local length = string.len(response_str)
-- check server response code
if length < 6 or
not (bit.band(string.byte(response_str, 6), 15) == 0) then
return nil
end
-- check server response code
if length < 6 or
not (bit.band(string.byte(response_str, 6), 15) == 0) then
return nil
end
-- parse zone information from all returned packets
for r in responses_iter(response_str) do
dump_zone_info(table, r)
end
-- parse zone information from all returned packets
for r in responses_iter(response_str) do
dump_zone_info(table, r)
end
soc:close()
return '\n' .. tab.dump(table)
soc:close()
return '\n' .. tab.dump(table)
end