mirror of
https://github.com/nmap/nmap.git
synced 2026-01-24 15:19:03 +00:00
o [NSE] Added script dns-blacklist that performs DNSBL checks of given or
scanned IP addresses against multiple DNSBL services. [Patrik]
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added script dns-blacklist that performs DNSBL checks of given or
|
||||
scanned IP addresses against multiple DNSBL services. [Patrik]
|
||||
|
||||
o [NSE] Applied patch to snmp-brute that solves problems with handling errors
|
||||
that occur when parsing files with community lists. [Duarte Silva]
|
||||
|
||||
|
||||
360
nselib/dnsbl.lua
Normal file
360
nselib/dnsbl.lua
Normal file
@@ -0,0 +1,360 @@
|
||||
--- A minimalistic DNS BlackList library implemented to facilitate querying
|
||||
-- various DNSBL services. The current list of services has been implemented
|
||||
-- based on the following compilations of services:
|
||||
-- * http://en.wikipedia.org/wiki/Comparison_of_DNS_blacklists
|
||||
-- * http://www.robtex.com
|
||||
-- * http://www.sdsc.edu/~jeff/spam/cbc.html
|
||||
--
|
||||
-- The library implements a helper class through which script may access
|
||||
-- the BL services. A typical script implementation could look like this:
|
||||
--
|
||||
-- <code>
|
||||
-- local helper = dnsbl.Helper:new("SPAM", "short")
|
||||
-- helper:setFilter('dnsbl.inps.de')
|
||||
-- local status, result = helper:checkBL(host.ip)
|
||||
-- ... formatting code ...
|
||||
-- </code>
|
||||
--
|
||||
-- @author "Patrik Karlsson <patrik@cqure.net>"
|
||||
--
|
||||
|
||||
module(... or "dnsbl", package.seeall)
|
||||
|
||||
require 'bit'
|
||||
|
||||
-- The services table contains a list of valid DNSBL providers
|
||||
-- Providers are categorized in categories that should contain services that
|
||||
-- do DNS blacklist checks for that particular category.
|
||||
--
|
||||
-- Each service should be stored under a key that specifies the service name
|
||||
-- and should contain:
|
||||
-- <code>ns_type</code> - A table with a record type as key and mode as value
|
||||
-- eg: { ["A"] = "short", ["TXT"] = "long" }.
|
||||
-- If only short queries are supported using A records, this argument may be
|
||||
-- omitted.
|
||||
--
|
||||
-- <code>resp_parser</code> - A function to parse the response received from
|
||||
-- the DNS query. The function should take two arguments:
|
||||
-- * <code>response</code> - the DNS response received by the server,
|
||||
-- typically a code represented by an IP.
|
||||
-- * <code>mode</code> - a string representing what mode (long|short) that
|
||||
-- the function should parse. If <code>ns_type</code> does not contain
|
||||
-- the TXT record, this argument and check can be omitted.
|
||||
-- When the short mode is used, the function should return a table containing
|
||||
-- the <code>state</code> field, or nil if the IP wasn't listed. When long
|
||||
-- mode is used, the function should return additional information using the
|
||||
-- <code>details</code> field. Eg:
|
||||
-- return { state = "SPAM" } -- short mode
|
||||
-- return { state = "PROXY", details = {
|
||||
-- "Proxy is working",
|
||||
-- "Proxy was scanned"
|
||||
-- } -- long mode
|
||||
--
|
||||
-- <code>fmt_query</code> - A function responsible for formatting the DNS
|
||||
-- query. When the default format is being used <reverse ip>.<servicename>
|
||||
-- eg: 4.3.2.1.spam.dnsbl.sorbs.net, this function can be omitted.
|
||||
--
|
||||
SERVICES = {
|
||||
|
||||
SPAM = {
|
||||
|
||||
["dnsbl.inps.de"] = {
|
||||
-- This service supports both long and short <code>mode</code>
|
||||
ns_type = {
|
||||
["short"] = "A",
|
||||
["long"] = "TXT",
|
||||
},
|
||||
-- sample fmt_query function, if no function is specified, the library
|
||||
-- will assume that the IP should be reversed add suffixed with the
|
||||
-- service name.
|
||||
fmt_query = function(ip)
|
||||
local rev_ip = dns.reverse(ip):match("^(.*)%.in%-addr%.arpa$")
|
||||
return ("%s.spam.dnsbl.sorbs.net"):format(rev_ip)
|
||||
end,
|
||||
-- This function parses the response and supports borth long and
|
||||
-- short mode.
|
||||
resp_parser = function(r, mode)
|
||||
local responses = {
|
||||
["127.0.0.2"] = "SPAM",
|
||||
}
|
||||
if ( ("short" == mode and r[1]) ) then
|
||||
return responses[r[1]]
|
||||
else
|
||||
return { state = "SPAM", details = { r[1] } }
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
["spam.dnsbl.sorbs.net"] = {
|
||||
ns_type = {
|
||||
["short"] = "A"
|
||||
},
|
||||
resp_parser = function(r)
|
||||
return ( r[1] == "127.0.0.6" and { state = "SPAM" } )
|
||||
end,
|
||||
},
|
||||
|
||||
["bl.nszones.com"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.2"] = "SPAM",
|
||||
["127.0.0.3"] = "DYNAMIC"
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
},
|
||||
|
||||
["all.spamrats.com"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.36"] = "DYNAMIC",
|
||||
["127.0.0.38"] = "SPAM",
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
},
|
||||
|
||||
["list.quorum.to"] = {
|
||||
resp_parser = function(r)
|
||||
return ( ( r[1] and r[1] == "127.0.0.2" ) and { state = "SPAM" } )
|
||||
end
|
||||
},
|
||||
|
||||
["sbl.spamhaus.org"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.2"] = "SPAM",
|
||||
["127.0.0.3"] = "SPAM",
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
},
|
||||
|
||||
["bl.spamcop.net"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.2"] = "SPAM",
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
},
|
||||
|
||||
["dnsbl.ahbl.org"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.4"] = "SPAM",
|
||||
["127.0.0.5"] = "SPAM",
|
||||
["127.0.0.6"] = "SPAM",
|
||||
["127.0.0.7"] = "SPAM",
|
||||
["127.0.0.8"] = "SPAM",
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
},
|
||||
|
||||
["l2.apews.org"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.2"] = "SPAM",
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
PROXY = {
|
||||
["dnsbl.tornevall.org"] = {
|
||||
resp_parser = function(r, mode)
|
||||
if ( "short" == mode and r[1] ) then
|
||||
return { state = "PROXY" }
|
||||
elseif ( "long" == mode ) then
|
||||
local responses = {
|
||||
[1] = "Proxy has been scanned",
|
||||
[2] = "Proxy is working",
|
||||
[4] = "?",
|
||||
[8] = "Proxy was tested, but timed out on connection",
|
||||
[16] = "Proxy was tested but failed at connection",
|
||||
[32] = "Proxy was tested but the IP was different",
|
||||
[64] = "IP marked as \"abusive host\"",
|
||||
[128] = "Proxy has a different anonymous-state"
|
||||
}
|
||||
|
||||
local code = tonumber(r[1]:match("%.(%d*)$"))
|
||||
local result = {}
|
||||
|
||||
for k, v in pairs(responses) do
|
||||
if ( bit.band( code, k ) == k ) then
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return { state = "PROXY", details = result }
|
||||
end
|
||||
end,
|
||||
},
|
||||
|
||||
["dnsbl.ahbl.org"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.3"] = "PROXY",
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
},
|
||||
|
||||
["http.dnsbl.sorbs.net"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.2"] = "PROXY",
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
},
|
||||
|
||||
["socks.dnsbl.sorbs.net"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.3"] = "PROXY",
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
},
|
||||
|
||||
["misc.dnsbl.sorbs.net"] = {
|
||||
resp_parser = function(r)
|
||||
local responses = {
|
||||
["127.0.0.4"] = "PROXY",
|
||||
}
|
||||
return ( r[1] and responses[r[1]] ) and { state = responses[r[1]] }
|
||||
end,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
Helper = {
|
||||
|
||||
-- Creates a new Helper instance
|
||||
-- @param category string containing a valid DNSBL service category
|
||||
-- @param mode string (short|long) specifying whether short or long
|
||||
-- results are to be returned
|
||||
-- @return o instance of Helper
|
||||
new = function(self, category, mode)
|
||||
local o = { category = category:upper(), mode = mode }
|
||||
assert(category and SERVICES[category:upper()], "Invalid category was supplied, aborting")
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Lists all DNSBL services for the category
|
||||
-- @return services table of service names
|
||||
listServices = function(self)
|
||||
local services = {}
|
||||
for svc in pairs(SERVICES[self.category]) do
|
||||
table.insert(services, svc)
|
||||
end
|
||||
return services
|
||||
end,
|
||||
|
||||
-- Validates the filter set by setFilter to make sure it contains only
|
||||
-- valid service names.
|
||||
-- @return status boolean, true on success false on failure
|
||||
-- @return err string containing an error message on failure
|
||||
validateFilter = function(self)
|
||||
|
||||
if ( not(self.filterstr) ) then
|
||||
return true
|
||||
end
|
||||
|
||||
local all = SERVICES[self.category]
|
||||
self.filter = {}
|
||||
for _, f in pairs(stdnse.strsplit(",%s*", self.filterstr)) do
|
||||
if ( not(SERVICES[self.category][f]) ) then
|
||||
self.filter = nil
|
||||
return false, ("Service does not exist '%s'"):format(f)
|
||||
end
|
||||
self.filter[f] = true
|
||||
end
|
||||
return true
|
||||
end,
|
||||
|
||||
-- Sets a new service filter to choose only a limited subset of services
|
||||
-- within a category.
|
||||
-- @param filter string containing a comma separated list of service names
|
||||
setFilter = function(self, filter) self.filterstr = filter end,
|
||||
|
||||
-- Gets a list of filtered services, or all services if no filter is in use
|
||||
-- @return services table containing a list of services
|
||||
getServices = function(self)
|
||||
if ( not(self:validateFilter()) ) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if ( self.filter ) then
|
||||
local filtered = {}
|
||||
for name, svc in pairs(SERVICES[self.category]) do
|
||||
if ( self.filter[name] ) then
|
||||
filtered[name] = svc
|
||||
end
|
||||
end
|
||||
return filtered
|
||||
else
|
||||
return SERVICES[self.category]
|
||||
end
|
||||
end,
|
||||
|
||||
-- Runs the DNS blacklist check for the given IP against all non-filtered
|
||||
-- services in the given category.
|
||||
-- @param ip string containing the IP address to check
|
||||
-- @return result table containing the results of the BL checks
|
||||
checkBL = function(self, ip)
|
||||
|
||||
local result = {}
|
||||
|
||||
for name, svc in pairs(self:getServices()) do
|
||||
--local ns_type = ( self.mode == "long" and (tabcontains(svc.ns_type or {}, 'TXT') and 'TXT' or 'A') or 'A')
|
||||
local ns_type = ( svc.ns_type and svc.ns_type[self.mode] ) and svc.ns_type[self.mode] or "A"
|
||||
local query
|
||||
|
||||
if ( svc.fmt_query ) then
|
||||
query = svc.fmt_query(ip)
|
||||
else
|
||||
local rev_ip = dns.reverse(ip):match("^(.*)%.in%-addr%.arpa$")
|
||||
query = ("%s.%s"):format(rev_ip, name)
|
||||
end
|
||||
|
||||
local status, answer = dns.query(query, {dtype=ns_type, retAll=true} )
|
||||
if ( status ) then
|
||||
local svc_result = svc.resp_parser(answer, self.mode)
|
||||
|
||||
if ( not(svc_result) ) then
|
||||
local resp = ( #answer > 0 and ("UNKNOWN (%s)"):format(answer[1]) or "UNKNOWN" )
|
||||
stdnse.print_debug(2, ("%s received %s"):format(name, resp))
|
||||
end
|
||||
|
||||
-- only add a record if the response could be parsed, some
|
||||
-- services, such as list.quorum.to, incorrectly return
|
||||
-- 127.0.0.0 when all is good.
|
||||
if ( svc_result ) then
|
||||
table.insert(result, { name = name, result = svc_result })
|
||||
end
|
||||
-- if status is false, and the response was "No Such Name", it
|
||||
-- simply means that the IP isn't listed, we haven't failed at
|
||||
-- this point. It would obviously be better to check this against
|
||||
-- an error code, or in some other way, but this is what we've got.
|
||||
elseif ( answer ~= "No Such Name" ) then
|
||||
table.insert(result, { name = name, result = { state = "FAIL" }})
|
||||
end
|
||||
end
|
||||
return result
|
||||
end,
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
157
scripts/dns-blacklist.nse
Normal file
157
scripts/dns-blacklist.nse
Normal file
@@ -0,0 +1,157 @@
|
||||
description = [[
|
||||
Checks an IP address against a number of different DNS spam blacklists and returns a list of services where the IP has been blacklisted.
|
||||
Checks may be limited by service category (eg: SPAM, PROXY) or to a specific service name.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap --script dns-blacklist --script-args='dns-blacklist.ip=<ip>'
|
||||
-- or
|
||||
-- nmap -sn <ip> --script dns-blacklist
|
||||
--
|
||||
-- @output
|
||||
-- Pre-scan script results:
|
||||
-- | dns-blacklist:
|
||||
-- | 1.2.3.4
|
||||
-- | PROXY
|
||||
-- | dnsbl.ahbl.org - PROXY
|
||||
-- | dnsbl.tornevall.org - PROXY
|
||||
-- | - IP marked as "abusive host".
|
||||
-- | - Proxy is working
|
||||
-- | - Proxy has been scanned
|
||||
-- | SPAM
|
||||
-- | dnsbl.inps.de - SPAM
|
||||
-- | - Spam Received See: http://www.sorbs.net/lookup.shtml?1.2.3.4
|
||||
-- | l2.apews.org - SPAM
|
||||
-- | list.quorum.to - SPAM
|
||||
-- | bl.spamcop.net - SPAM
|
||||
-- |_ spam.dnsbl.sorbs.net - SPAM
|
||||
--
|
||||
-- @args dns-blacklist.ip string containing the IP to check only needed if
|
||||
-- running the script as a prerule.
|
||||
|
||||
-- @args dns-blacklist.mode string containing either "short" or "long"
|
||||
-- long mode can sometimes provide additional information to why an IP
|
||||
-- has been blacklisted. (default: long)
|
||||
--
|
||||
-- @args dns-blacklist.list lists all services that are available for a
|
||||
-- certain category.
|
||||
--
|
||||
-- @args dns-blacklist.services string containing a comma-separated list of
|
||||
-- services to query. (default: all)
|
||||
--
|
||||
-- @args dns-blacklist.category string containing the service category to query
|
||||
-- eg. spam or proxy (default: all)
|
||||
--
|
||||
--
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"external", "safe"}
|
||||
|
||||
require 'dns'
|
||||
require 'dnsbl'
|
||||
require 'tab'
|
||||
require 'ipOps'
|
||||
|
||||
-- The script can be run either as a host- or pre-rule
|
||||
hostrule = function() return true end
|
||||
prerule = function() return true end
|
||||
|
||||
local arg_IP = stdnse.get_script_args(SCRIPT_NAME .. ".ip")
|
||||
local arg_mode = stdnse.get_script_args(SCRIPT_NAME .. ".mode") or "long"
|
||||
local arg_list = stdnse.get_script_args(SCRIPT_NAME .. ".list")
|
||||
local arg_services = stdnse.get_script_args(SCRIPT_NAME .. ".services")
|
||||
local arg_category = stdnse.get_script_args(SCRIPT_NAME .. ".category") or "all"
|
||||
|
||||
local function listServices()
|
||||
local result = {}
|
||||
if ( "all" == arg_category ) then
|
||||
for cat in pairs(dnsbl.SERVICES) do
|
||||
local helper = dnsbl.Helper:new(cat, arg_mode)
|
||||
local cat_res= helper:listServices()
|
||||
cat_res.name = cat
|
||||
table.insert(result, cat_res)
|
||||
end
|
||||
else
|
||||
result = dnsbl.Helper:new(arg_category, arg_mode):listServices()
|
||||
end
|
||||
return stdnse.format_output(true, result)
|
||||
end
|
||||
|
||||
local function formatResult(result)
|
||||
local output = {}
|
||||
for _, svc in ipairs(result) do
|
||||
if ( svc.result.details ) then
|
||||
svc.result.details.name = ("%s - %s"):format(svc.name, svc.result.state)
|
||||
table.insert(output, svc.result.details)
|
||||
else
|
||||
table.insert(output, ("%s - %s"):format(svc.name, svc.result.state))
|
||||
end
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
dnsblAction = function(host)
|
||||
|
||||
-- if the list argument was given, just list the services and abort
|
||||
if ( arg_list ) then
|
||||
return listServices()
|
||||
end
|
||||
|
||||
local helper
|
||||
if ( arg_services and ( not(arg_category) or "all" == arg_category:lower() ) ) then
|
||||
return "\n ERROR: A service filter can't be used without a specific category"
|
||||
elseif( "all" ~= arg_category ) then
|
||||
helper = dnsbl.Helper:new(arg_category, arg_mode)
|
||||
helper:setFilter(arg_services)
|
||||
local status, err = helper:validateFilter()
|
||||
if ( not(status) ) then
|
||||
return ("\n ERROR: %s"):format(err)
|
||||
end
|
||||
end
|
||||
|
||||
local output = {}
|
||||
if ( helper ) then
|
||||
local result = helper:checkBL(host.ip)
|
||||
if ( #result == 0 ) then return end
|
||||
output = formatResult(result)
|
||||
else
|
||||
for cat in pairs(dnsbl.SERVICES) do
|
||||
helper = dnsbl.Helper:new(cat, arg_mode)
|
||||
local result = helper:checkBL(host.ip)
|
||||
local out_part = formatResult(result)
|
||||
if ( #out_part > 0 ) then
|
||||
out_part.name = cat
|
||||
table.insert(output, out_part)
|
||||
end
|
||||
end
|
||||
if ( #output == 0 ) then return end
|
||||
end
|
||||
|
||||
if ( "prerule" == SCRIPT_TYPE ) then
|
||||
output.name = host.ip
|
||||
end
|
||||
|
||||
return stdnse.format_output(true, output)
|
||||
end
|
||||
|
||||
|
||||
-- execute the action function corresponding to the current rule
|
||||
action = function(...)
|
||||
|
||||
if ( arg_mode ~= "short" and arg_mode ~= "long" ) then
|
||||
return "\n ERROR: Invalid argument supplied, mode should be either 'short' or 'long'"
|
||||
end
|
||||
|
||||
if ( not(ipOps.todword(arg_IP)) ) then
|
||||
return "\n ERROR: Invalid IP address was supplied"
|
||||
end
|
||||
|
||||
if ( arg_IP and "prerule" == SCRIPT_TYPE ) then
|
||||
return dnsblAction( { ip = arg_IP } )
|
||||
elseif ( "hostrule" == SCRIPT_TYPE ) then
|
||||
return dnsblAction(...)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -47,6 +47,7 @@ Entry { filename = "daytime.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "db2-das-info.nse", categories = { "discovery", "safe", "version", } }
|
||||
Entry { filename = "db2-discover.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "dhcp-discover.nse", categories = { "discovery", "intrusive", } }
|
||||
Entry { filename = "dns-blacklist.nse", categories = { "external", "safe", } }
|
||||
Entry { filename = "dns-brute.nse", categories = { "discovery", "intrusive", } }
|
||||
Entry { filename = "dns-cache-snoop.nse", categories = { "discovery", "intrusive", } }
|
||||
Entry { filename = "dns-fuzz.nse", categories = { "fuzzer", "intrusive", } }
|
||||
|
||||
Reference in New Issue
Block a user