diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
commit | 0d47952611198ef6b1163f366dc03922d20b1475 (patch) | |
tree | 3d840a3b8c0daef0754707bfb9f5e873b6b1ac13 /scripts/dns-cache-snoop.nse | |
parent | Initial commit. (diff) | |
download | nmap-upstream.tar.xz nmap-upstream.zip |
Adding upstream version 7.94+git20230807.3be01efb1+dfsg.upstream/7.94+git20230807.3be01efb1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'scripts/dns-cache-snoop.nse')
-rw-r--r-- | scripts/dns-cache-snoop.nse | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/scripts/dns-cache-snoop.nse b/scripts/dns-cache-snoop.nse new file mode 100644 index 0000000..14fcb58 --- /dev/null +++ b/scripts/dns-cache-snoop.nse @@ -0,0 +1,233 @@ +local dns = require "dns" +local formulas = require "formulas" +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" +local string = require "string" +local table = require "table" + +description = [[ +Performs DNS cache snooping against a DNS server. + +There are two modes of operation, controlled by the +<code>dns-cache-snoop.mode</code> script argument. In +<code>nonrecursive</code> mode (the default), queries are sent to the +server with the RD (recursion desired) flag set to 0. The server should +respond positively to these only if it has the domain cached. In +<code>timed</code> mode, the mean and standard deviation response times +for a cached domain are calculated by sampling the resolution of a name +(www.google.com) several times. Then, each domain is resolved and the +time taken compared to the mean. If it is less than one standard +deviation over the mean, it is considered cached. The <code>timed</code> +mode inserts entries in the cache and can only be used reliably once. + +The default list of domains to check consists of the top 50 most popular +sites, each site being listed twice, once with "www." and once without. +Use the <code>dns-cache-snoop.domains</code> script argument to use a +different list. +]] + +--- +-- @args dns-cache-snoop.mode which of two supported snooping methods to +-- use. <code>nonrecursive</code>, the default, checks if the server +-- returns results for non-recursive queries. Some servers may disable +-- this. <code>timed</code> measures the difference in time taken to +-- resolve cached and non-cached hosts. This mode will pollute the DNS +-- cache and can only be used once reliably. +-- @args dns-cache-snoop.domains an array of domain to check in place of +-- the default list. +-- +-- @usage +-- nmap -sU -p 53 --script dns-cache-snoop.nse --script-args 'dns-cache-snoop.mode=timed,dns-cache-snoop.domains={host1,host2,host3}' <target> +-- +-- @output +-- PORT STATE SERVICE REASON +-- 53/udp open domain udp-response +-- | dns-cache-snoop: 10 of 100 tested domains are cached. +-- | www.google.com +-- | facebook.com +-- | www.facebook.com +-- | www.youtube.com +-- | yahoo.com +-- | twitter.com +-- | www.twitter.com +-- | www.google.com.hk +-- | www.google.co.uk +-- |_www.linkedin.com + + +author = "Eugene V. Alexeev" + +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" + +categories = {"intrusive", "discovery"} + +portrule = shortport.port_or_service(53, "domain", "udp") + +local DOMAINS = {} +local MODE = "nonrecursive" +-- This domain is used as a default cached entry in timed mode. +local TIMED_DUMMY_DOMAIN = "www.google.com" +-- How many samples to collect for the time taken to resolve the dummy domain. +local TIMED_NUM_SAMPLES = 25 +-- In timed mode, times below mean + TIMED_MULTIPLIER * stddev are +-- accepted as cached. Using one standard deviation gives us a roughly +-- 84% chance that a domain with the same response time as the reference +-- domain will be detected as cached. +local TIMED_MULTIPLIER = 1.0 + +-- This list is the first 50 entries of +-- http://s3.amazonaws.com/alexa-static/top-1m.csv.zip on 2013-08-08. +local ALEXA_DOMAINS = { + "google.com", + "facebook.com", + "youtube.com", + "yahoo.com", + "baidu.com", + "wikipedia.org", + "amazon.com", + "qq.com", + "live.com", + "linkedin.com", + "twitter.com", + "blogspot.com", + "taobao.com", + "google.co.in", + "bing.com", + "yahoo.co.jp", + "yandex.ru", + "wordpress.com", + "sina.com.cn", + "vk.com", + "ebay.com", + "google.de", + "tumblr.com", + "msn.com", + "google.co.uk", + "googleusercontent.com", + "ask.com", + "mail.ru", + "google.com.br", + "163.com", + "google.fr", + "pinterest.com", + "google.com.hk", + "hao123.com", + "microsoft.com", + "google.co.jp", + "xvideos.com", + "google.ru", + "weibo.com", + "craigslist.org", + "paypal.com", + "instagram.com", + "amazon.co.jp", + "google.it", + "imdb.com", + "blogger.com", + "google.es", + "apple.com", + "conduit.com", + "sohu.com", +} + +-- Construct the default list of domains. +for _, domain in ipairs(ALEXA_DOMAINS) do + DOMAINS[#DOMAINS + 1] = domain + if not string.match(domain, "^www%.") then + DOMAINS[#DOMAINS + 1] = "www." .. domain + end +end + +local function nonrecursive_mode(host, port, domains) + local cached = {} + + for _,domain in ipairs(domains) do + if dns.query(domain, {host = host.ip, port = port.number, tries = 0, norecurse=true}) then + cached[#cached + 1] = domain + end + end + + return cached +end + +-- Return the time taken (in seconds) to resolve the given domain, or nil if +-- it could not be resolved. +local function timed_query(host, port, domain) + local start, stop + + start = nmap.clock_ms() + if dns.query(domain, {host = host.ip, port = port.number, tries = 0, norecurse = false}) then + stop = nmap.clock_ms() + return (stop - start) / 1000 + else + return nil + end +end + +local function timed_mode(host, port, domains) + local cached = {} + local i, t + + -- Insert in the cache. + timed_query(host, port, TIMED_DUMMY_DOMAIN) + + -- Measure how long it takes to resolve on average. + local times = {} + for i = 1, TIMED_NUM_SAMPLES do + t = timed_query(host, port, TIMED_DUMMY_DOMAIN) + if t then + times[#times + 1] = t + end + end + local mean, stddev = formulas.mean_stddev(times) + local cutoff = mean + stddev * TIMED_MULTIPLIER + stdnse.debug1("reference %s: mean %g stddev %g cutoff %g", TIMED_DUMMY_DOMAIN, mean, stddev, cutoff) + + -- Now try all domains one by one. + for _, domain in ipairs(domains) do + t = timed_query(host, port, domain) + if t then + if t < cutoff then + stdnse.debug1("%s: %g is cached (cutoff %g)", domain, t, cutoff) + cached[#cached + 1] = domain + else + stdnse.debug1("%s: %g not cached (cutoff %g)", domain, t, cutoff) + end + end + end + + return cached +end + +action = function(host, port) + local domains = DOMAINS + local mode = MODE + + local args = nmap.registry.args + if args then + if args["dns-cache-snoop.mode"] then + mode = args["dns-cache-snoop.mode"] + end + if args["dns-cache-snoop.domains"] then + domains = args["dns-cache-snoop.domains"] + end + end + + local cached + + mode = string.lower(mode) + if mode == "nonrecursive" then + cached = nonrecursive_mode(host, port, domains) + elseif mode == "timed" then + cached = timed_mode(host, port, domains) + else + return string.format("Error: \"%s\" is not a known mode. Use \"nonrecursive\" or \"timed\".") + end + + if #cached > 0 then + nmap.set_port_state(host, port, "open") + end + + return string.format("%d of %d tested domains are cached.\n", #cached, #domains) .. table.concat(cached, "\n") +end |