diff --git a/CHANGELOG b/CHANGELOG index 8e9db348b..8cfcbd795 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,11 @@ # Nmap Changelog ($Id$); -*-text-*- +o Added dns-safe-recursion-port and dns-safe-recursion-txid (non + default NSE scripts) which use the 3rd party dns-oarc.net to test + the source port and transaction ID randomness of a discovered DNS + server (assuming it allows recursion at all). These scripts were + contributed by Brandon Enright. + o Added some Windows and MinGW compatibility patches submitted by Gisle Vanem. diff --git a/scripts/dns-safe-recursion-port.nse b/scripts/dns-safe-recursion-port.nse new file mode 100644 index 000000000..8e544dc94 --- /dev/null +++ b/scripts/dns-safe-recursion-port.nse @@ -0,0 +1,172 @@ +id = "DNS source port randomness" + +description = "Queries porttest.dns-oarc.net to check for the predictable-port DNS recursion vulnerability. Predictable source ports can make a DNS server vulnerable to cache poisoning attacks (CVE-2008-1447)" + +license = "Script: Same as Nmap--See http://nmap.org/book/man-legal.html\n" .. + "porttest.dns-oarc.net: https://www.dns-oarc.net/oarc/services/porttest" + +author = "Script: Brandon Enright \n" .. + "porttest.dns-oarc.net: Duane Wessels " + +-- 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 = {"intrusive"} + +require "bit" +require "comm" +require "shortport" + +portrule = shortport.portnumber(53, "udp") + +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" .. + string.char( 0x08) .. "dns-oarc" .. + string.char( 0x03) .. "net" .. + string.char( 0x00, -- Name terminator + 0x00, 0x10, -- Type (TXT) + 0x00, 0x01) -- Class (IN) + + -- This doesn't work without the bytes= setting... + local status, result = comm.exchange(host, port, query, {proto="udp", + bytes=1, + timeout=20000}) + + -- Fail gracefully + if not status then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: TIMEOUT" + else + return + end + 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 (string.len(result) < 5) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Malformed response" + else + return + end + end + + -- Check TXID + if (string.byte(result, 1) ~= 0xbe + or string.byte(result, 2) ~= 0xef) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Invalid Transaction ID" + else + return + end + end + + -- Check response flag and recursion + if not (bit.band(string.byte(result, 3), 0x80) == 0x80 + and bit.band(string.byte(result, 4), 0x80) == 0x80) then + if (nmap.verbosity() >= 1 or nmap.debugging() >= 1) then + return "ERROR: Server refused recursion" + else + return + end + end + + -- Check error flag + if (bit.band(string.byte(result, 4), 0x0F) ~= 0x00) then + if (nmap.verbosity() >= 1 or nmap.debugging() >= 1) then + return "ERROR: Server failure" + else + return + end + 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 + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Response did not include expected answers" + else + return + end + end + + -- We need a minimum of 128 bytes... + if (string.len(result) < 128) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Truncated response" + else + return + end + 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 + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Answer record not of type TXT" + else + return + end + end + + -- Check for IN + if (string.byte(result, 120) ~= 0x00 + or string.byte(result, 121) ~= 0x01) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Answer record not of type IN" + else + return + end + end + + -- Get TXT length + local txtlen = string.byte(result, 128) + + -- We now need a minimum of 128 + txtlen bytes + 1... + if (string.len(result) < 128 + txtlen) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Truncated response" + else + return + end + end + + -- GET TXT record + local txtrd = string.sub(result, 129, 128 + txtlen) + + return txtrd +end diff --git a/scripts/dns-safe-recursion-txid.nse b/scripts/dns-safe-recursion-txid.nse new file mode 100644 index 000000000..a958903fd --- /dev/null +++ b/scripts/dns-safe-recursion-txid.nse @@ -0,0 +1,172 @@ +id = "DNS TXID randomness" + +description = "Queries txidtest.dns-oarc.net to check for the predictable-TXID DNS recursion vulnerability. Predictable TXID values can make a DNS server vulnerable to cache poisoning attacks (CVE-2008-1447)" + +license = "Script: Same as Nmap--See http://nmap.org/book/man-legal.html\n" .. + "txidtest.dns-oarc.net: https://www.dns-oarc.net/oarc/services/txidtest" + +author = "Script: Brandon Enright \n" .. + "txidtest.dns-oarc.net: Duane Wessels " + +-- This script uses (with permission) Duane Wessels' txidtest.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 = {"intrusive"} + +require "bit" +require "comm" +require "shortport" + +portrule = shortport.portnumber(53, "udp") + +action = function(host, port) + + -- TXID: 0xbabe + -- Flags: 0x0100 + -- Questions: 1 + -- Answer RRs: 0 + -- Authority RRs: 0 + -- Additional RRs: 0 + + -- Query: + -- Name: txidtest, dns-oarc, net + -- Type: TXT (0x0010) + -- Class: IN (0x0001) + + local query = string.char( 0xba, 0xbe, -- TXID + 0x01, 0x00, -- Flags + 0x00, 0x01, -- Questions + 0x00, 0x00, -- Answer RRs + 0x00, 0x00, -- Authority RRs + 0x00, 0x00, -- Additional RRs + 0x08) .. "txidtest" .. + string.char( 0x08) .. "dns-oarc" .. + string.char( 0x03) .. "net" .. + string.char( 0x00, -- Name terminator + 0x00, 0x10, -- Type (TXT) + 0x00, 0x01) -- Class (IN) + + -- This doesn't work without the bytes= setting... + local status, result = comm.exchange(host, port, query, {proto="udp", + bytes=1, + timeout=20000}) + + -- Fail gracefully + if not status then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: TIMEOUT" + else + return + end + 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 (string.len(result) < 5) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Malformed response" + else + return + end + end + + -- Check TXID + if (string.byte(result, 1) ~= 0xba + or string.byte(result, 2) ~= 0xbe) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Invalid Transaction ID" + else + return + end + end + + -- Check response flag and recursion + if not (bit.band(string.byte(result, 3), 0x80) == 0x80 + and bit.band(string.byte(result, 4), 0x80) == 0x80) then + if (nmap.verbosity() >= 1 or nmap.debugging() >= 1) then + return "ERROR: Server refused recursion" + else + return + end + end + + -- Check error flag + if (bit.band(string.byte(result, 4), 0x0F) ~= 0x00) then + if (nmap.verbosity() >= 1 or nmap.debugging() >= 1) then + return "ERROR: Server failure" + else + return + end + 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 + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Response did not include expected answers" + else + return + end + end + + -- We need a minimum of 128 bytes... + if (string.len(result) < 128) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Truncated response" + else + return + end + 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 + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Answer record not of type TXT" + else + return + end + end + + -- Check for IN + if (string.byte(result, 120) ~= 0x00 + or string.byte(result, 121) ~= 0x01) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Answer record not of type IN" + else + return + end + end + + -- Get TXT length + local txtlen = string.byte(result, 128) + + -- We now need a minimum of 128 + txtlen bytes + 1... + if (string.len(result) < 128 + txtlen) then + if (nmap.verbosity() >= 2 or nmap.debugging() >= 1) then + return "ERROR: Truncated response" + else + return + end + end + + -- GET TXT record + local txtrd = string.sub(result, 129, 128 + txtlen) + + return txtrd +end diff --git a/scripts/script.db b/scripts/script.db index fff6ee1b4..c7e1173b8 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -3,6 +3,9 @@ Entry{ category = "intrusive", filename = "dns-test-open-recursion.nse" } Entry{ category = "default", filename = "RealVNC_auth_bypass.nse" } Entry{ category = "malware", filename = "RealVNC_auth_bypass.nse" } Entry{ category = "vuln", filename = "RealVNC_auth_bypass.nse" } +Entry{ category = "intrusive", filename = "dns-safe-recursion-port.nse" } +Entry{ category = "intrusive", filename = "SNMPcommunitybrute.nse" } +Entry{ category = "auth", filename = "SNMPcommunitybrute.nse" } Entry{ category = "default", filename = "showOwner.nse" } Entry{ category = "safe", filename = "showOwner.nse" } Entry{ category = "default", filename = "SSLv2-support.nse" } @@ -18,6 +21,7 @@ Entry{ category = "safe", filename = "rpcinfo.nse" } Entry{ category = "discovery", filename = "rpcinfo.nse" } Entry{ category = "auth", filename = "bruteTelnet.nse" } Entry{ category = "intrusive", filename = "bruteTelnet.nse" } +Entry{ category = "intrusive", filename = "dns-safe-recursion-txid.nse" } Entry{ category = "default", filename = "SMTPcommands.nse" } Entry{ category = "discovery", filename = "SMTPcommands.nse" } Entry{ category = "safe", filename = "SMTPcommands.nse" } @@ -31,6 +35,7 @@ Entry{ category = "demo", filename = "chargenTest.nse" } Entry{ category = "malware", filename = "strangeSMTPport.nse" } Entry{ category = "version", filename = "iax2Detect.nse" } Entry{ category = "demo", filename = "showSMTPVersion.nse" } +Entry{ category = "discovery", filename = "ASN.nse" } Entry{ category = "default", filename = "showHTMLTitle.nse" } Entry{ category = "demo", filename = "showHTMLTitle.nse" } Entry{ category = "safe", filename = "showHTMLTitle.nse" } @@ -56,6 +61,9 @@ Entry{ category = "discovery", filename = "finger.nse" } Entry{ category = "demo", filename = "showHTTPVersion.nse" } Entry{ category = "default", filename = "SSHv1-support.nse" } Entry{ category = "safe", filename = "SSHv1-support.nse" } +Entry{ category = "default", filename = "popcapa.nse" } +Entry{ category = "intrusive", filename = "brutePOP3.nse" } +Entry{ category = "auth", filename = "brutePOP3.nse" } Entry{ category = "default", filename = "MySQLinfo.nse" } Entry{ category = "discovery", filename = "MySQLinfo.nse" } Entry{ category = "safe", filename = "MySQLinfo.nse" }