summaryrefslogtreecommitdiffstats
path: root/scripts/dns-random-srcport.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/dns-random-srcport.nse')
-rw-r--r--scripts/dns-random-srcport.nse153
1 files changed, 153 insertions, 0 deletions
diff --git a/scripts/dns-random-srcport.nse b/scripts/dns-random-srcport.nse
new file mode 100644
index 0000000..b2fa016
--- /dev/null
+++ b/scripts/dns-random-srcport.nse
@@ -0,0 +1,153 @@
+local comm = require "comm"
+local nmap = require "nmap"
+local shortport = require "shortport"
+local string = require "string"
+local stdnse = require "stdnse"
+
+description = [[
+Checks a DNS server for the predictable-port recursion vulnerability.
+Predictable source ports can make a DNS server vulnerable to cache poisoning
+attacks (see CVE-2008-1447).
+
+The script works by querying porttest.dns-oarc.net (see
+https://www.dns-oarc.net/oarc/services/porttest). Be aware that any
+targets against which this script is run will be sent to and
+potentially recorded by one or more DNS servers and the porttest
+server. In addition your IP address will be sent along with the
+porttest query to the DNS server running on the target.
+]]
+
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+
+author = [[
+Script: Brandon Enright <bmenrigh@ucsd.edu>
+porttest.dns-oarc.net: Duane Wessels <wessels@dns-oarc.net>
+]]
+
+---
+-- @usage
+-- nmap -sU -p 53 --script=dns-random-srcport <target>
+-- @output
+-- PORT STATE SERVICE REASON
+-- 53/udp open domain udp-response
+-- |_dns-random-srcport: X.X.X.X is GREAT: 26 queries in 1.2 seconds from 26 ports with std dev 17905
+
+-- This script uses (with permission) Duane Wessels' porttest.dns-oarc.net
+-- service. Duane/OARC believe the service is valuable to the community
+-- and have no plans to ever turn the service off.
+-- The likely long-term availability makes this script a good candidate
+-- for inclusion in Nmap proper.
+
+categories = {"external", "intrusive"}
+
+
+portrule = shortport.portnumber(53, "udp")
+
+local function fail (err) return stdnse.format_output(false, err) end
+
+action = function(host, port)
+
+ -- TXID: 0xbeef
+ -- Flags: 0x0100
+ -- Questions: 1
+ -- Answer RRs: 0
+ -- Authority RRs: 0
+ -- Additional RRs: 0
+
+ -- Query:
+ -- Name: porttest, dns-oarc, net
+ -- Type: TXT (0x0010)
+ -- Class: IN (0x0001)
+
+ local query = string.char( 0xbe, 0xef, -- TXID
+ 0x01, 0x00, -- Flags
+ 0x00, 0x01, -- Questions
+ 0x00, 0x00, -- Answer RRs
+ 0x00, 0x00, -- Authority RRs
+ 0x00, 0x00, -- Additional RRs
+ 0x08) .. "porttest" ..
+ "\x08" .. "dns-oarc" ..
+ "\x03" .. "net" ..
+ string.char( 0x00, -- Name terminator
+ 0x00, 0x10, -- Type (TXT)
+ 0x00, 0x01) -- Class (IN)
+
+ local status, result = comm.exchange(host, port, query, {proto="udp",
+ timeout=20000})
+
+ -- Fail gracefully
+ if not status then
+ return fail(result)
+ end
+
+ -- Update the port
+ nmap.set_port_state(host, port, "open")
+
+ -- Now we need to "parse" the results to check to see if they are good
+
+ -- We need a minimum of 5 bytes...
+ if (#result < 5) then
+ return fail("Malformed response")
+ end
+
+ -- Check TXID
+ if (string.byte(result, 1) ~= 0xbe
+ or string.byte(result, 2) ~= 0xef) then
+ return fail("Invalid Transaction ID")
+ end
+
+ -- Check response flag and recursion
+ if not ((string.byte(result, 3) & 0x80) == 0x80
+ and (string.byte(result, 4) & 0x80) == 0x80) then
+ return fail("Server refused recursion")
+ end
+
+ -- Check error flag
+ if (string.byte(result, 4) & 0x0F) ~= 0x00 then
+ return fail("Server failure")
+ end
+
+ -- Check for two Answer RRs and 1 Authority RR
+ if (string.byte(result, 5) ~= 0x00
+ or string.byte(result, 6) ~= 0x01
+ or string.byte(result, 7) ~= 0x00
+ or string.byte(result, 8) ~= 0x02) then
+ return fail("Response did not include expected answers")
+ end
+
+ -- We need a minimum of 128 bytes...
+ if (#result < 128) then
+ return fail("Truncated response")
+ end
+
+ -- Here is the really fragile part. If the DNS response changes
+ -- in any way, this won't work and will fail.
+ -- Jump to second answer and check to see that it is TXT, IN
+ -- then grab the length and display that text...
+
+ -- Check for TXT
+ if (string.byte(result, 118) ~= 0x00
+ or string.byte(result, 119) ~= 0x10)
+ then
+ return fail("Answer record not of type TXT")
+ end
+
+ -- Check for IN
+ if (string.byte(result, 120) ~= 0x00
+ or string.byte(result, 121) ~= 0x01) then
+ return fail("Answer record not of type IN")
+ end
+
+ -- Get TXT length
+ local txtlen = string.byte(result, 128)
+
+ -- We now need a minimum of 128 + txtlen bytes + 1...
+ if (#result < 128 + txtlen) then
+ return fail("Truncated response")
+ end
+
+ -- GET TXT record
+ local txtrd = string.sub(result, 129, 128 + txtlen)
+
+ return txtrd
+end