diff options
Diffstat (limited to '')
-rw-r--r-- | scripts/dns-brute.nse | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/scripts/dns-brute.nse b/scripts/dns-brute.nse new file mode 100644 index 0000000..30207cb --- /dev/null +++ b/scripts/dns-brute.nse @@ -0,0 +1,328 @@ +local coroutine = require "coroutine" +local dns = require "dns" +local io = require "io" +local math = require "math" +local nmap = require "nmap" +local stdnse = require "stdnse" +local string = require "string" +local stringaux = require "stringaux" +local table = require "table" +local target = require "target" +local rand = require "rand" + +description = [[ +Attempts to enumerate DNS hostnames by brute force guessing of common +subdomains. With the <code>dns-brute.srv</code> argument, dns-brute will also +try to enumerate common DNS SRV records. + +Wildcard records are listed as "*A" and "*AAAA" for IPv4 and IPv6 respectively. +]] +-- 2011-01-26 + +--- +-- @usage +-- nmap --script dns-brute --script-args dns-brute.domain=foo.com,dns-brute.threads=6,dns-brute.hostlist=./hostfile.txt,newtargets -sS -p 80 +-- nmap --script dns-brute www.foo.com +-- @args dns-brute.hostlist The filename of a list of host strings to try. +-- Defaults to "nselib/data/vhosts-default.lst" +-- @args dns-brute.threads Thread to use (default 5). +-- @args dns-brute.srv Perform lookup for SRV records +-- @args dns-brute.srvlist The filename of a list of SRV records to try. +-- Defaults to "nselib/data/dns-srv-names" +-- @args dns-brute.domain Domain name to brute force if no host is specified +-- +-- @see dns-nsec3-enum.nse +-- @see dns-ip6-arpa-scan.nse +-- @see dns-nsec-enum.nse +-- @see dns-zone-transfer.nse +-- +-- @output +-- Pre-scan script results: +-- | dns-brute: +-- | DNS Brute-force hostnames +-- | www.foo.com - 127.0.0.1 +-- | mail.foo.com - 127.0.0.2 +-- | blog.foo.com - 127.0.1.3 +-- | ns1.foo.com - 127.0.0.4 +-- | admin.foo.com - 127.0.0.5 +-- |_ *A: 127.0.0.123 +-- +-- @xmloutput +-- <table key="DNS Brute-force hostnames"> +-- <table> +-- <elem key="address">127.0.0.1</elem> +-- <elem key="hostname">www.foo.com</elem> +-- </table> +-- <table> +-- <elem key="address">127.0.0.2</elem> +-- <elem key="hostname">mail.foo.com</elem> +-- </table> +-- <table> +-- <elem key="address">127.0.1.3</elem> +-- <elem key="hostname">blog.foo.com</elem> +-- </table> +-- <table> +-- <elem key="address">127.0.0.4</elem> +-- <elem key="hostname">ns1.foo.com</elem> +-- </table> +-- <table> +-- <elem key="address">127.0.0.5</elem> +-- <elem key="hostname">admin.foo.com</elem> +-- </table> +-- <elem key="*A">127.0.0.123</elem> +-- </table> +-- <table key="SRV results"></table> + +author = "Cirrus" + +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" + +categories = {"intrusive", "discovery"} + +prerule = function() + if not stdnse.get_script_args("dns-brute.domain") then + stdnse.debug1("Skipping '%s' %s, 'dns-brute.domain' argument is missing.", SCRIPT_NAME, SCRIPT_TYPE) + return false + end + return true +end + +hostrule = function(host) + return true +end + +local function guess_domain(host) + local name + + name = stdnse.get_hostname(host) + if name and name ~= host.ip then + return string.match(name, "%.([^.]+%..+)%.?$") or string.match(name, "^([^.]+%.[^.]+)%.?$") + else + return nil + end +end + +-- Single DNS lookup, returning all results. dtype should be e.g. "A", "AAAA". +local function resolve(host, dtype) + local status, result = dns.query(host, {dtype=dtype,retAll=true}) + return status and result or false +end + +local function array_iter(array, i, j) + return coroutine.wrap(function () + while i <= j do + coroutine.yield(array[i]) + i = i + 1 + end + end) +end + +local record_mt = { + __tostring = function(t) + return ("%s - %s"):format(t.hostname, t.address) + end +} + +local function make_record(hostn, addr) + local record = { hostname=hostn, address=addr } + setmetatable(record, record_mt) + return record +end + +local function thread_main(domainname, results, name_iter) + local condvar = nmap.condvar( results ) + for name in name_iter do + for _, dtype in ipairs({"A", "AAAA"}) do + local res = resolve(name..'.'..domainname, dtype) + if(res) then + table.sort(res) + if results["*" .. dtype] ~= res[1] then + for _,addr in ipairs(res) do + local hostn = name..'.'..domainname + if target.ALLOW_NEW_TARGETS then + stdnse.debug1("Added target: "..hostn) + local status,err = target.add(hostn) + end + stdnse.debug2("Hostname: "..hostn.." IP: "..addr) + results[#results+1] = make_record(hostn, addr) + end + end + end + end + end + condvar("signal") +end + +local function srv_main(domainname, srvresults, srv_iter) + local condvar = nmap.condvar( srvresults ) + for name in srv_iter do + local res = resolve(name..'.'..domainname, "SRV") + if(res) then + for _,addr in ipairs(res) do + local hostn = name..'.'..domainname + addr = stringaux.strsplit(":",addr) + for _, dtype in ipairs({"A", "AAAA"}) do + local srvres = resolve(addr[4], dtype) + if(srvres) then + for srvhost,srvip in ipairs(srvres) do + if target.ALLOW_NEW_TARGETS then + stdnse.debug1("Added target: "..srvip) + local status,err = target.add(srvip) + end + stdnse.debug1("Hostname: "..hostn.." IP: "..srvip) + srvresults[#srvresults+1] = make_record(hostn, srvip) + end + end + end + end + end + end + condvar("signal") +end + +local function detect_wildcard(domainname, record) + local rand_host1 = rand.random_alpha(24).."."..domainname + local rand_host2 = rand.random_alpha(24).."."..domainname + local res1 = resolve(rand_host1, record) + + stdnse.debug1("Detecting wildcard for \"%s\" records using random hostname \"%s\".", record, rand_host1) + if res1 then + stdnse.debug1("Random hostname resolved. Comparing to second random hostname \"%s\".", rand_host2) + local res2 = resolve(rand_host2, record) + table.sort(res1) + table.sort(res2) + + if (res1[1] == res2[1]) then + stdnse.debug1("Both random hostnames resolved to the same IP. Wildcard detected.") + return res1[1] + end + end + + return nil +end + +action = function(host) + local domainname = stdnse.get_script_args('dns-brute.domain') + if not domainname then + domainname = guess_domain(host) + end + + if not domainname then + return string.format("Can't guess domain of \"%s\"; use %s.domain script argument.", stdnse.get_hostname(host), SCRIPT_NAME) + end + + if not nmap.registry.bruteddomains then + nmap.registry.bruteddomains = {} + end + + if nmap.registry.bruteddomains[domainname] then + stdnse.debug1("Skipping already-bruted domain %s", domainname) + return nil + end + + nmap.registry.bruteddomains[domainname] = true + stdnse.debug1("Starting dns-brute at: "..domainname) + local max_threads = tonumber( stdnse.get_script_args('dns-brute.threads') ) or 5 + local dosrv = stdnse.get_script_args("dns-brute.srv") or false + stdnse.debug1("THREADS: "..max_threads) + -- First look for dns-brute.hostlist + local fileName = stdnse.get_script_args('dns-brute.hostlist') + -- Check fetchfile locations, then relative paths + local commFile = (fileName and nmap.fetchfile(fileName)) or fileName + -- Finally, fall back to vhosts-default.lst + commFile = commFile or nmap.fetchfile("nselib/data/vhosts-default.lst") + local hostlist = {} + if commFile then + for l in io.lines(commFile) do + if not l:match("#!comment:") then + table.insert(hostlist, l) + end + end + else + stdnse.debug1("Cannot find hostlist file, quitting") + return + end + + local threads, results, srvresults = {}, {}, {} + for _, dtype in ipairs({"A", "AAAA"}) do + results["*" .. dtype] = detect_wildcard(domainname, dtype) + end + + local condvar = nmap.condvar( results ) + local i = 1 + local howmany = math.floor(#hostlist/max_threads)+1 + stdnse.debug1("Hosts per thread: "..howmany) + repeat + local j = math.min(i+howmany, #hostlist) + local name_iter = array_iter(hostlist, i, j) + threads[stdnse.new_thread(thread_main, domainname, results, name_iter)] = true + i = j+1 + until i > #hostlist + local done + -- wait for all threads to finish + while( not(done) ) do + done = true + for thread in pairs(threads) do + if (coroutine.status(thread) ~= "dead") then done = false end + end + if ( not(done) ) then + condvar("wait") + end + end + + if(dosrv) then + -- First look for dns-brute.srvlist + fileName = stdnse.get_script_args('dns-brute.srvlist') + -- Check fetchfile locations, then relative paths + commFile = (fileName and nmap.fetchfile(fileName)) or fileName + -- Finally, fall back to dns-srv-names + commFile = commFile or nmap.fetchfile("nselib/data/dns-srv-names") + local srvlist = {} + if commFile then + for l in io.lines(commFile) do + if not l:match("#!comment:") then + table.insert(srvlist, l) + end + end + + i = 1 + threads = {} + howmany = math.floor(#srvlist/max_threads)+1 + condvar = nmap.condvar( srvresults ) + stdnse.debug1("SRV's per thread: "..howmany) + repeat + local j = math.min(i+howmany, #srvlist) + local name_iter = array_iter(srvlist, i, j) + threads[stdnse.new_thread(srv_main, domainname, srvresults, name_iter)] = true + i = j+1 + until i > #srvlist + local done + -- wait for all threads to finish + while( not(done) ) do + done = true + for thread in pairs(threads) do + if (coroutine.status(thread) ~= "dead") then done = false end + end + if ( not(done) ) then + condvar("wait") + end + end + else + stdnse.debug1("Cannot find srvlist file, skipping") + end + end + + local response = stdnse.output_table() + if(#results==0) then + setmetatable(results, { __tostring = function(t) return "No results." end }) + end + response["DNS Brute-force hostnames"] = results + if(dosrv) then + if(#srvresults==0) then + setmetatable(srvresults, { __tostring = function(t) return "No results." end }) + end + response["SRV results"] = srvresults + end + return response +end + |