1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-10 09:49:05 +00:00

Add the latest ASN.nse script. This version uses the new Nmap-specific query

servers, groups output intelligently, and supports IPv6. See sample output at
http://seclists.org/nmap-dev/2008/q3/0675.html.
This commit is contained in:
david
2008-09-06 02:47:46 +00:00
parent 0399bc71c5
commit 205e7ab28b
3 changed files with 796 additions and 199 deletions

View File

@@ -324,7 +324,7 @@ o The loading of the nmap-services file has been made much
o Added a script (ASN.nse) which uses Team Cymru's DNS interface to
determine the routing AS numbers of scanned IP addresses. They even
set up a special domain just for Nmap queries. The script is still
experimental and non-default. [Michael]
experimental and non-default. [Jah, Michael]
o The shtool build helper script has been updated to version 2.0.8. An
older version of shutil caused installation to fail when the locale

View File

@@ -1,158 +1,755 @@
--[[
id = "AS Numbers"
description = [[
This script performs IP address to Autonomous System Numbers (ASN) lookups. It
sends DNS TXT queries to a DNS server which in turn queries a third party service
provided by Team Cymru (team-cymru.org) using an in-addr.arpa style zone set-up
especially for use by Nmap.
\n
The respnses to these queries contain both Origin and Peer ASNs and their descriptions,
displayed along with the BG Prefix and Country Code.
\n
The script caches results to reduce the number of queries and should perform a single
query for all scanned targets in a BG Prefix present in Team Cymru's database.
\n\n
Please be aware that any targets for which a query is performed will be revealed
to a Team Cymru.
]]
Query Autonomous System Numbers Based on IP
-------------------------------------------
Uses a 3rd party service provided by team-cymru.org to
find the autonomous system numbers of a specific IP.
This scan is performed on all hosts in a portscan but
will only output a result once for every netblock.
Be aware that because this scan uses a 3rd party
service it may result in a loss of privacy, all hosts
that you scan with this script will be sent to team-
cymru.org.
--]]
---
-- @usage
-- nmap <target> --script asn
--
-- @args dns Optional recursive nameserver. e.g. --script-args dns=192.168.1.1
--
-- @output
-- Host script results:
-- \n| AS Numbers:
-- \n| BGP: 64.13.128.0/21 | Country: US
-- \n| Origin AS: 10565 SVCOLO-AS - Silicon Valley Colocation, Inc.
-- \n| Peer AS: 3561 6461
-- \n| BGP: 64.13.128.0/18 | Country: US
-- \n| Origin AS: 10565 SVCOLO-AS - Silicon Valley Colocation, Inc.
-- \n|_ Peer AS: 174 2914 6461
--
id = "ASN"
description = "This script discovers the autonomous system numbers of the \
netblock ranges that you scanned. It will return other information as well \
such as a country code, and BGP prefix. \
Caution: This script access a 3rd party database provided by team-cymru.com \
to discover this information. Using this script will expose the hosts that \
you scan to team-cymru.com in order to get the results. \
Usage: nmap <target> --script asn --script-args dns=<recursion_enabled_dns_server>"
author = "Jah, Michael"
author = "jah, Michael"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery"}
runlevel = 1
require "comm"
require "ipOps"
hostrule = function( host )
return true
end
local dns = require "dns"
local comm = require "comm"
local mutex = nmap.mutex( id )
if not nmap.registry.asn then
nmap.registry.asn = {}
nmap.registry.asn.cache = {}
nmap.registry.asn.descr = {}
end
local mutex = nmap.mutex( id )
---
-- This script will run for any non-private IP address.
hostrule = function( host )
return not isPrivate( host.ip )
end
---
-- Cached results are checked before sending a query for the target and extracting the
-- relevent information from the response. Mutual exclusion is used so that results can be
-- cached and so a single thread will be active at any time.
-- @param host Host Table.
-- @return Formatted answers or nil on NXDOMAIN/errors.
action = function( host )
-- get args or die
local dns_server
if nmap.registry.args.dns then
dns_server = nmap.registry.args.dns
else
return
end
-- wait
mutex "lock"
-- check for cached data
for _, cache in ipairs( nmap.registry.asn.cache ) do
if ip_in_net( host.ip, cache.bgp) then
mutex "done"
return " \nBGP Prefix: " .. cache.bgp .. "\nAS number: " .. cache.asn .. "\nCountry Code: " .. cache.co_id
local in_cache, records
local combined_records = {}
in_cache, records = check_cache( host.ip )
records = records or {}
if not in_cache then
---
-- @class table
-- @name cymru
-- Team Cymru zones for rDNS like queries. The zones are as follows:
-- \n nmap.asn.cymru.com for IPv4 to Origin AS lookup.
-- \n peer-nmap.asn.cymru.com for IPv4 to Peer AS lookup.
-- \n nmap6.asn.cymru.com for IPv6 to Origin AS lookup.
local cymru = { [4] = { ["Origin"] = ".nmap.asn.cymru.com", ["Peer"] = ".peer-nmap.asn.cymru.com" },
[6] = { ["Origin"] = ".nmap6.asn.cymru.com" }
}
local zone_repl, IPv = "%.in%-addr%.arpa", 4
if host.ip:match( ":" ) then
zone_repl, IPv = "%.ip6%.arpa", 6
end
-- name to query for
local dname = reverse( host.ip )
-- perform queries for each applicable zone
for asn_type, zone in pairs( cymru[IPv] ) do
-- replace arpa with cymru zone
local temp = dname
dname = dname:gsub( zone_repl, zone )
-- send query and recognise and organise fields from response
local success, retval = result_recog( ip_to_asn( dname ), asn_type, records )
-- if success then records = retval end
-- un-replace arpa zone
dname = temp
end
-- combine records into unique BGP
for _, record in ipairs( records ) do
if not combined_records[record.cache_bgp] then
combined_records[record.cache_bgp] = record
elseif combined_records[record.cache_bgp].asn_type ~= record.asn_type then
-- origin before peer.
if record.asn_type == "Origin" then
combined_records[record.cache_bgp].asn = { unpack( record.asn ), unpack( combined_records[record.cache_bgp].asn ) }
else
combined_records[record.cache_bgp].asn = { unpack( combined_records[record.cache_bgp].asn ), unpack( record.asn ) }
end
end
end
-- format data
-- cache combined records
for _, rec in pairs( combined_records ) do
table.insert( nmap.registry.asn.cache, rec )
end
else -- records were in the cache
combined_records = records
end
-- format each combined_record for output
local output = {}
for _, rec in pairs( combined_records ) do
local r = {}
if rec.bgp then r[#r+1] = rec.bgp end
if rec.co then r[#r+1] = rec.co end
output[#output+1] = ( "%s\n %s" ):format( table.concat( r, " | " ), table.concat( rec.asn, "\n " ) )
end
mutex "done"
if type( output ) ~= "table" or #output == 0 then return nil end
-- sort BGP asc.
table.sort( output, function(a,b) return (get_prefix_length(a) or 0) > (get_prefix_length(b) or 0) end )
-- return combined and formatted answers
return (" \n%s"):format( table.concat( output, "\n" ) )
end
---
-- Checks whether the target IP address is within any BGP prefixes for which a query has
-- already been performed and returns any applicable answers.
-- @param ip String representing the target IP address.
-- @return Boolean True if there are cached answers for the supplied target, otherwise
-- false.
-- @return Table containing a string for each answer or nil if there are none.
function check_cache( ip )
local ret = {}
for _, cache_entry in ipairs( nmap.registry.asn.cache ) do
if ip_in_range( ip, cache_entry.cache_bgp ) then
ret[#ret+1] = cache_entry
end
end
if #ret > 0 then return true, ret end
return false, nil
end
---
-- Extracts fields from the supplied DNS answer sections.
-- @param answers Table containing string DNS answers.
-- @param asn_type String denoting whether the query is for Origin or Peer ASN.
-- @param recs Table of existing recognised answers to which to add (ref to actions() records{}.
-- @return Boolean true if successful otherwise false.
function result_recog( answers, asn_type, recs )
if type( answers ) ~= "table" or #answers == 0 then return false end
for _, answer in ipairs( answers ) do
local t = {}
t[4], t[3], t[2], t[1] = host.ip:match( "([^\.]*)\.([^\.]*)\.([^\.]*)\.([^\.]*)" )
local tsoh = labels( t )
local z = { "nmap", "asn", "cymru", "com" }
local zone = labels( z )
-- break the answer up into fields and strip whitespace
local fields = { answer:match( ("([^|]*)|" ):rep(3) ) }
for i, field in ipairs( fields ) do
fields[i] = field:gsub( "^%s*(.-)%s*$", "%1" )
end
-- assign fields with labels to table
t.cache_bgp = fields[2]
t.asn_type = asn_type
t.asn = { asn_type .. " AS: " .. fields[1] }
t.bgp = "BGP: " .. fields[2]
if fields[3] ~= "" then t.co = "Country: " .. fields[3] end
recs[#recs+1] = t
-- lookup AS descriptions for Origin AS numbers
local asn_descr = nmap.registry.asn.descr
local u = {}
if asn_type == "Origin" then
for num in fields[1]:gmatch( "%d+" ) do
if not asn_descr[num] then
asn_descr[num] = asn_description( num )
end
u[#u+1] = ( "%s AS: %s%s%s" ):format( asn_type, num, ( asn_descr[num] ~= "" and " - " ) or "", asn_descr[num] )
end
t.asn = { table.concat(u, "\n " ) }
end
end
local t_id = string.char( tonumber( t[2] ), tonumber( t[3] ) ) -- not at all random...
local dns_std = string.char( 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 )
local null_char = string.char( 0x00 )
local qtype = string.char( 0x00, 0x10 )
local qclass = string.char( 0x00, 0x01 )
local query = tsoh .. zone .. null_char .. qtype .. qclass
return true
local data = t_id .. dns_std .. query
end
-- send data
---
-- Performs an IP address to ASN lookup. See http://www.team-cymru.org/Services/ip-to-asn.html#dns
-- @param query String - PTR like DNS query.
-- @return Table containing string answers or Boolean false.
function ip_to_asn( query )
if type( query ) ~= "string" or query == "" then
return nil
end
-- dns query options
local options = {}
options.proto = "udp"
options.lines = 1
options.timeout = 1000
local status, result = comm.exchange( dns_server, 53, data, options )
if not status then
mutex "done"
return
options.dtype = "TXT"
options.retAll = true
if type( nmap.registry.args.dns ) == "string" and nmap.registry.args.dns ~= "" then
options.host = nmap.registry.args.dns
options.port = 53
end
-- read result - this method is tenuous!!
local _, offset = string.find( result, query )
local line = string.sub( result, offset + 13 )
fields = {line:match( ("([^|]*)|"):rep(3) )}
local decoded_response, other_response = dns.query( query, options)
-- cache result
local blob = {}
blob.bgp = fields[2]:gsub( "^%s*(.-)%s*$", "%1" )
blob.asn = fields[1]:gsub( "^%s*[^0](.-)%s*$", "%1" )
blob.co_id = fields[3]:gsub( "^%s*(.-)%s*$", "%1" )
table.insert( nmap.registry.asn.cache, blob )
mutex "done"
-- return result
return " \nBGP Prefix: " .. blob.bgp .. "\nAS number: " .. blob.asn .. "\nCountry Code: " .. blob.co_id
return decoded_response
end
-- labels
-- given a table of strings, return a string made up of concateneted labels
-- where each label consists of a length value (cast as char) followed by that number of characters.
function labels( t )
local ret = ""
for _, v in ipairs(t) do
ret = ret .. string.char( string.len(v) ) .. v
end
return ret
---
-- Performs an AS Number to AS Description lookup.
-- @param asn String AS Number
-- @return String Description or ""
function asn_description( asn )
if type( asn ) ~= "string" or asn == "" then
return ""
end
-- ip_in_net
-- returns true if the supplied ip address falls inside the supplied range
function ip_in_net(ip, net)
local i, j, net_lo, net_hi, dw_ip
local m_dotted = "(%d+%.%d+%.%d+%.%d+)[%s]*[-][%s]*(%d+%.%d+%.%d+%.%d+)"
local m_cidr = "(%d+)[.]*(%d*)[.]*(%d*)[.]*(%d*)[/]+(%d+)"
if net:match(m_dotted) then
net_lo, net_hi = net:match(m_dotted)
net_lo = ipOps.todword(net_lo)
net_hi = ipOps.todword(net_hi)
elseif net:match(m_cidr) then
net_lo, net_hi = two_dwords(net, m_cidr)
-- dns query options
local options = {}
options.dtype = "TXT"
if type( nmap.registry.args.dns ) == "string" and nmap.registry.args.dns ~= "" then
options.host = nmap.registry.args.dns
options.port = 53
end
dw_ip = ipOps.todword(ip)
if net_lo <= dw_ip and dw_ip <= net_hi then return true end
-- send query
local query = ( "AS%s.asn.cymru.com" ):format( asn )
local decoded_response, other_response = dns.query( query, options)
if type( decoded_response ) ~= "string" then
return ""
end
return decoded_response:match( "|%s*([^|$]+)%s*$" ) or ""
end
-- *** UTILITY FUNCTIONS ***
-- remove when these functions are available in libraries
---
-- Formats IP for reverse lookup.
-- @param ip String IP address.
-- @return "Domain" style representation of IP as subdomain of in-addr.arpa or ip6.arpa
function reverse(ip)
ip = expand_ip(ip)
if type(ip) ~= "string" then return nil end
local delim = "%."
local arpa = ".in-addr.arpa"
if ip:match(":") then
delim = ":"
arpa = ".ip6.arpa"
end
local ipParts = stdnse.strsplit(delim, ip)
if #ipParts == 8 then
-- padding
local mask = "0000"
for i, part in ipairs(ipParts) do
ipParts[i] = mask:sub(1, string.len(mask) - string.len(part)) .. part
end
-- 32 parts from 8
local temp = {}
for i, hdt in ipairs(ipParts) do
for part in hdt:gmatch("%x") do
temp[#temp+1] = part
end
end
ipParts = temp
end
local ipReverse = {}
for i = #ipParts, 1, -1 do
table.insert(ipReverse, ipParts[i])
end
return table.concat(ipReverse, ".") .. arpa
end
---
-- Checks to see if the supplied IP address is part of the following non-internet-routable address spaces:
-- IPv4 Loopback (RFC3330),
-- IPv4 Private Use (RFC1918),
-- IPv4 Link Local (RFC3330),
-- IPv6 Unspecified and Loopback (RFC3513),
-- IPv6 Unique Local Unicast (RFC4193),
-- IPv6 Link Local Unicast (RFC4291)
-- @param ip String representing an IPv4 or IPv6 address. Shortened notation is permitted.
-- @usage local is_private = isPrivate( "192.168.1.1" )
-- @return Boolean True or False (or nil in case of an error).
-- @return Nil (or String error message in case of an error).
isPrivate = function( ip )
ip, err = expand_ip( ip )
if err then return nil, err end
local ipv4_private = { "10/8", "127/8", "169.254/16", "172.15/12", "192.168/16" }
local ipv6_private = { "::/127", "FC00::/7", "FE80::/10" }
local t, is_private = {}
if ip:match( ":" ) then
t = ipv6_private
else
t = ipv4_private
end
for _, range in ipairs( t ) do
is_private, err = ip_in_range( ip, range )
-- return as soon as is_private is true or err
if is_private then return true end
if err then return nil, err end
end
return false
end
---
-- Checks whether the supplied IP address is within the supplied Range of IP addresses if they belong to the same address family.
-- @param ip String representing an IPv4 or IPv6 address. Shortened notation is permitted.
-- @param range String representing a range of IPv4 or IPv6 addresses in first-last or cidr notation (e.g. "192.168.1.1 - 192.168.255.255" or "2001:0A00::/23").
-- @usage if ip_in_range( "192.168.1.1", "192/8" ) then ...
-- @return Boolean True or False (or nil in case of an error).
-- @return Nil (or String error message in case of an error).
ip_in_range = function( ip, range )
local first, last, err = get_ips_from_range( range )
if err then return nil, err end
ip, err = expand_ip( ip )
if err then return nil, err end
if ( ip:match( ":" ) and not first:match( ":" ) ) or ( not ip:match( ":" ) and first:match( ":" ) ) then
return nil, "Error in ip_in_range: IP address is of a different address family to Range."
end
err = {}
local ip_ge_first, ip_le_last
ip_ge_first, err[#err+1] = compare_ip( ip, "ge", first )
ip_le_last, err[#err+1] = compare_ip( ip, "le", last )
if #err > 0 then
return nil, table.concat( err, " " )
end
if ip_ge_first and ip_le_last then
return true
else
return false
end
-- two_dwords
-- returns the two ip addresses at either end of a cidr range, as dwords
function two_dwords(str, patt)
local a, b, c, d, e, lo_net, host
a, b, c, d, e = str:match(patt)
local ipt = {b, c, d}
local strip = ""
for _, cap in ipairs(ipt) do
if cap == "" then cap = "0" end
strip = strip .. "." .. cap
end
lo_net = a .. strip
if e ~= "" then e = tonumber(e)
if e and e <=32 then
host = 32 - e end
---
-- Expands an IP address supplied in shortened notation.
-- Serves also to check the well-formedness of an IP address.
-- Note: IPv4in6 notated addresses will be returned in pure IPv6 notation unless the IPv4 portion
-- is shortened and does not contain a dot - in which case the address will be treated as IPv6.
-- @param ip String representing an IPv4 or IPv6 address in shortened or full notation.
-- @usage local ip = expand_ip( "2001::" )
-- @return String representing a fully expanded IPv4 or IPv6 address (or nil in case of an error).
-- @return Nil (or String error message in case of an error).
expand_ip = function( ip )
if type( ip ) ~= "string" or ip == "" then
return nil, "Error in expand_ip: Expected IP address as a string."
end
return ipOps.todword(lo_net), ipOps.todword(lo_net) + 2^host - 1
local err4 = "Error in expand_ip: An address assumed to be IPv4 was malformed."
if not ip:match( ":" ) then
-- ipv4: missing octets should be "0" appended
if ip:match( "[^\.0-9]" ) then
return nil, err4
end
local octets = {}
for octet in string.gfind( ip, "%d+" ) do
if tonumber( octet, 10 ) > 255 then return nil, err4 end
octets[#octets+1] = octet
end
if #octets > 4 then return nil, err4 end
while #octets < 4 do
octets[#octets+1] = "0"
end
return ( table.concat( octets, "." ) )
end
if ip:match( "[^\.:%x]" ) then
return nil, ( err4:gsub( "IPv4", "IPv6" ) )
end
-- preserve ::
ip = string.gsub(ip, "::", ":z:")
-- get a table of each hexadectet
local hexadectets = {}
for hdt in string.gfind( ip, "[\.z%x]+" ) do
hexadectets[#hexadectets+1] = hdt
end
-- deal with IPv4in6 (last hexadectet only)
local t = {}
if hexadectets[#hexadectets]:match( "[\.]+" ) then
hexadectets[#hexadectets], err = expand_ip( hexadectets[#hexadectets] )
if err then return nil, ( err:gsub( "IPv4", "IPv4in6" ) ) end
t = stdnse.strsplit( "[\.]+", hexadectets[#hexadectets] )
for i, v in ipairs( t ) do
t[i] = tonumber( v, 10 )
end
hexadectets[#hexadectets] = stdnse.tohex( 256*t[1]+t[2] )
hexadectets[#hexadectets+1] = stdnse.tohex( 256*t[3]+t[4] )
end
-- deal with :: and check for invalid address
local z_done = false
for index, value in ipairs( hexadectets ) do
if value:match( "[\.]+" ) then
-- shouldn't have dots at this point
return nil, ( err4:gsub( "IPv4", "IPv6" ) )
elseif value == "z" and z_done then
-- can't have more than one ::
return nil, ( err4:gsub( "IPv4", "IPv6" ) )
elseif value == "z" and not z_done then
z_done = true
hexadectets[index] = "0"
local bound = 8 - #hexadectets
for i = 1, bound, 1 do
table.insert( hexadectets, index+i, "0" )
end
elseif tonumber( value, 16 ) > 65535 then
-- more than FFFF!
return nil, ( err4:gsub( "IPv4", "IPv6" ) )
end
end
-- make sure we have exactly 8 hexadectets
if #hexadectets > 8 then return nil, ( err4:gsub( "IPv4", "IPv6" ) ) end
while #hexadectets < 8 do
hexadectets[#hexadectets+1] = "0"
end
return ( table.concat( hexadectets, ":" ) )
end
---
-- Compares two IP addresses (from the same address family).
-- @param left String representing an IPv4 or IPv6 address. Shortened notation is permitted.
-- @param op A comparison operator which may be one of the following strings: "eq", "ge", "le", "gt" or "lt" (respectively ==, >=, <=, >, <).
-- @param right String representing an IPv4 or IPv6 address. Shortened notation is permitted.
-- @usage if compare_ip( "2001::DEAD:0:0:0", "eq", "2001:0:0:0:DEAD::" ) then ...
-- @return Boolean True or False (or nil in case of an error).
-- @return Nil (or String error message in case of an error).
compare_ip = function( left, op, right )
if type( left ) ~= "string" or type( right ) ~= "string" then
return nil, "Error in compare_ip: Expected IP address as a string."
end
if ( left:match( ":" ) and not right:match( ":" ) ) or ( not left:match( ":" ) and right:match( ":" ) ) then
return nil, "Error in compare_ip: IP addresses must be from the same address family."
end
if op == "lt" or op == "le" then
left, right = right, left
elseif op ~= "eq" and op ~= "ge" and op ~= "gt" then
return nil, "Error in compare_ip: Invalid Operator."
end
local err ={}
left, err[#err+1] = ip_to_bin( left )
right, err[#err+1] = ip_to_bin( right )
if #err > 0 then
return nil, table.concat( err, " " )
end
if string.len( left ) ~= string.len( right ) then
-- shouldn't happen...
return nil, "Error in compare_ip: Binary IP addresses were of different lengths."
end
-- equal?
if ( op == "eq" or op == "le" or op == "ge" ) and left == right then
return true
elseif op == "eq" then
return false
end
-- starting from the leftmost bit, subtract the bit in right from the bit in left
local compare
for i = 1, string.len( left ), 1 do
compare = tonumber( string.sub( left, i, i ) ) - tonumber( string.sub( right, i, i ) )
if compare == 1 then
return true
elseif compare == -1 then
return false
end
end
return false
end
---
-- Returns the first and last IP addresses in the supplied range of addresses.
-- @param range String representing a range of IPv4 or IPv6 addresses in either cidr or first-last notation.
-- @usage first, last = get_ips_from_range( "192.168.0.0/16" )
-- @return String representing the first address in the supplied range (or nil in case of an error).
-- @return String representing the last address in the supplied range (or nil in case of an error).
-- @return Nil (or String error message in case of an error).
get_ips_from_range = function( range )
if type( range ) ~= "string" then
return nil, nil, "Error in get_ips_from_range: Expected a range as a string."
end
local first, last, prefix
if range:match( "/" ) then
first, prefix = range:match( "([%x%d:\.]+)/(%d+)" )
elseif range:match( "-" ) then
first, last = range:match( "([%x%d:\.]+)%s*\-%s*([%x%d:\.]+)" )
end
local err = {}
if first and ( last or prefix ) then
first, err[#err+1] = expand_ip( first )
else
return nil, nil, "Error in get_ips_from_range: The range supplied could not be interpreted."
end
if last then
last, err[#err+1] = expand_ip( last )
elseif first and prefix then
last, err[#err+1] = get_last_ip( first, prefix )
end
if first and last then
if ( first:match( ":" ) and not last:match( ":" ) ) or ( not first:match( ":" ) and last:match( ":" ) ) then
return nil, nil, "Error in get_ips_from_range: First IP address is of a different address family to last IP address."
end
return first, last
else
return nil, nil, table.concat( err, " " )
end
end
---
-- Calculates the last IP address of a range of addresses given an IP address in the range and prefix length for that range.
-- @param ip String representing an IPv4 or IPv6 address. Shortened notation is permitted.
-- @param prefix Decimal number or a string representing a decimal number corresponding to a Prefix length.
-- @usage last = get_last_ip( "192.0.0.0", 26 )
-- @return String representing the last IP address of the range denoted by the supplied parameters (or nil in case of an error).
-- @return Nil (or String error message in case of an error).
get_last_ip = function( ip, prefix )
local first, err = ip_to_bin( ip )
if err then return nil, err end
prefix = tonumber( prefix )
if not prefix or ( prefix < 0 ) or ( prefix > string.len( first ) ) then
return nil, "Error in get_last_ip: Invalid prefix length."
end
local hostbits = string.sub( first, prefix + 1 )
hostbits = string.gsub( hostbits, "0", "1" )
local last = string.sub( first, 1, prefix ) .. hostbits
last, err = bin_to_ip( last )
if err then return nil, err end
return last
end
---
-- Converts an IP address into a string representing the address as binary digits.
-- @param ip String representing an IPv4 or IPv6 address. Shortened notation is permitted.
-- @usage bit_string = ip_to_bin( "2001::" )
-- @return String representing the supplied IP address as 32 or 128 binary digits (or nil in case of an error).
-- @return Nil (or String error message in case of an error).
ip_to_bin = function( ip )
ip, err = expand_ip( ip )
if err then return nil, err end
local t, mask = {}
if not ip:match( ":" ) then
-- ipv4 string
for octet in string.gfind( ip, "%d+" ) do
t[#t+1] = stdnse.tohex( octet )
end
mask = "00"
else
-- ipv6 string
for hdt in string.gfind( ip, "%x+" ) do
t[#t+1] = hdt
end
mask = "0000"
end
-- padding
for i, v in ipairs( t ) do
t[i] = mask:sub( 1, string.len( mask ) - string.len( v ) ) .. v
end
return hex_to_bin( table.concat( t ) )
end
---
-- Converts a string representing binary digits into an IP address.
-- @param binstring String representing an IP address as 32 or 128 binary digits.
-- @usage ip = bin_to_ip( "01111111000000000000000000000001" )
-- @return String representing an IP address (or nil in case of an error).
-- @return Nil (or String error message in case of an error).
bin_to_ip = function( binstring )
if type( binstring ) ~= "string" or binstring:match( "[^01]+" ) then
return nil, "Error in bin_to_ip: Expected string of binary digits."
end
if string.len( binstring ) == 32 then
af = 4
elseif string.len( binstring ) == 128 then
af = 6
else
return nil, "Error in bin_to_ip: Expected exactly 32 or 128 binary digits."
end
t = {}
if af == 6 then
local pattern = string.rep( "[01]", 16 )
for chunk in string.gfind( binstring, pattern ) do
t[#t+1] = stdnse.tohex( tonumber( chunk, 2 ) )
end
return table.concat( t, ":" )
end
if af == 4 then
local pattern = string.rep( "[01]", 8 )
for chunk in string.gfind( binstring, pattern ) do
t[#t+1] = tonumber( chunk, 2 ) .. ""
end
return table.concat( t, "." )
end
end
---
-- Converts a string representing a hexadecimal number into a string representing that number as binary digits.
-- Each hex digit results in four bits - this function is really just a wrapper around stdnse.tobinary().
-- @param hex String representing a hexadecimal number.
-- @usage bin_string = hex_to_bin( "F00D" )
-- @return String representing the supplied number in binary digits (or nil in case of an error).
-- @return Nil (or String error message in case of an error).
hex_to_bin = function( hex )
if type( hex ) ~= "string" or hex == "" or hex:match( "[^%x]+" ) then
return nil, "Error in hex_to_bin: Expected string representing a hexadecimal number."
end
local t, mask, binchar = {}, "0000"
for hexchar in string.gfind( hex, "%x" ) do
binchar = stdnse.tobinary( tonumber( hexchar, 16 ) )
t[#t+1] = mask:sub( 1, string.len( mask ) - string.len( binchar ) ) .. binchar
end
return table.concat( t )
end
---
-- Calculates the prefix length for the given IP address range.
-- @param range String representing an IP address range
-- @return Number - prefix length of the range
function get_prefix_length( range )
if type( range ) ~= "string" or range == "" then return nil end
local first, last, err = get_ips_from_range( range )
if err then return nil end
first = ip_to_bin( first ):reverse()
last = ip_to_bin( last ):reverse()
local hostbits = 0
for pos = 1, string.len( first ), 1 do
if first:sub( pos, pos ) == "0" and last:sub( pos, pos ) == "1" then
hostbits = hostbits + 1
else
break
end
end
return ( string.len( first ) - hostbits )
end

View File

@@ -1,88 +1,88 @@
Entry{ category = "default", filename = "dns-test-open-recursion.nse" }
Entry{ category = "intrusive", filename = "dns-test-open-recursion.nse" }
Entry{ category = "default", filename = "showOwner.nse" }
Entry{ category = "safe", filename = "showOwner.nse" }
Entry{ category = "demo", filename = "daytimeTest.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" }
Entry{ category = "safe", filename = "SSLv2-support.nse" }
Entry{ category = "malware", filename = "ircZombieTest.nse" }
Entry{ category = "version", filename = "skype_v2-version.nse" }
Entry{ category = "discovery", filename = "HTTPtrace.nse" }
Entry{ category = "demo", filename = "echoTest.nse" }
Entry{ category = "default", filename = "UPnP-info.nse" }
Entry{ category = "safe", filename = "UPnP-info.nse" }
Entry{ category = "default", filename = "rpcinfo.nse" }
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" }
Entry{ category = "default", filename = "robots.nse" }
Entry{ category = "safe", filename = "robots.nse" }
Entry{ category = "default", filename = "zoneTrans.nse" }
Entry{ category = "intrusive", filename = "zoneTrans.nse" }
Entry{ category = "discovery", filename = "zoneTrans.nse" }
Entry{ category = "discovery", filename = "whois.nse" }
Entry{ category = "safe", filename = "whois.nse" }
Entry{ category = "discovery", filename = "ripeQuery.nse" }
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" }
Entry{ category = "discovery", filename = "promiscuous.nse" }
Entry{ category = "version", filename = "netbios-smb-os-discovery.nse" }
Entry{ category = "default", filename = "anonFTP.nse" }
Entry{ category = "auth", filename = "anonFTP.nse" }
Entry{ category = "intrusive", filename = "anonFTP.nse" }
Entry{ category = "intrusive", filename = "SQLInject.nse" }
Entry{ category = "vuln", filename = "SQLInject.nse" }
Entry{ category = "auth", filename = "bruteTelnet.nse" }
Entry{ category = "intrusive", filename = "bruteTelnet.nse" }
Entry{ category = "discovery", filename = "HTTPtrace.nse" }
Entry{ category = "demo", filename = "SMTP_openrelay_test.nse" }
Entry{ category = "default", filename = "nbstat.nse" }
Entry{ category = "discovery", filename = "nbstat.nse" }
Entry{ category = "safe", filename = "nbstat.nse" }
Entry{ category = "default", filename = "HTTPAuth.nse" }
Entry{ category = "auth", filename = "HTTPAuth.nse" }
Entry{ category = "intrusive", filename = "HTTPAuth.nse" }
Entry{ category = "default", filename = "finger.nse" }
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 = "default", filename = "SNMPsysdescr.nse" }
Entry{ category = "discovery", filename = "SNMPsysdescr.nse" }
Entry{ category = "safe", filename = "SNMPsysdescr.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" }
Entry{ category = "default", filename = "ftpbounce.nse" }
Entry{ category = "intrusive", filename = "ftpbounce.nse" }
Entry{ category = "auth", filename = "xamppDefaultPass.nse" }
Entry{ category = "vuln", filename = "xamppDefaultPass.nse" }
Entry{ category = "intrusive", filename = "HTTPpasswd.nse" }
Entry{ category = "vuln", filename = "HTTPpasswd.nse" }
Entry{ category = "demo", filename = "showSSHVersion.nse" }
Entry{ category = "version", filename = "PPTPversion.nse" }
Entry{ category = "default", filename = "ircServerInfo.nse" }
Entry{ category = "discovery", filename = "ircServerInfo.nse" }
Entry{ category = "default", filename = "dns-test-open-recursion.nse" }
Entry{ category = "intrusive", filename = "dns-test-open-recursion.nse" }
Entry{ category = "demo", filename = "chargenTest.nse" }
Entry{ category = "default", filename = "showHTMLTitle.nse" }
Entry{ category = "demo", filename = "showHTMLTitle.nse" }
Entry{ category = "safe", filename = "showHTMLTitle.nse" }
Entry{ category = "default", filename = "MSSQLm.nse" }
Entry{ category = "discovery", filename = "MSSQLm.nse" }
Entry{ category = "intrusive", filename = "MSSQLm.nse" }
Entry{ category = "demo", filename = "echoTest.nse" }
Entry{ category = "default", filename = "SSHv1-support.nse" }
Entry{ category = "safe", filename = "SSHv1-support.nse" }
Entry{ category = "default", filename = "MySQLinfo.nse" }
Entry{ category = "discovery", filename = "MySQLinfo.nse" }
Entry{ category = "safe", filename = "MySQLinfo.nse" }
Entry{ category = "auth", filename = "xamppDefaultPass.nse" }
Entry{ category = "vuln", filename = "xamppDefaultPass.nse" }
Entry{ category = "default", filename = "SSLv2-support.nse" }
Entry{ category = "safe", filename = "SSLv2-support.nse" }
Entry{ category = "default", filename = "zoneTrans.nse" }
Entry{ category = "intrusive", filename = "zoneTrans.nse" }
Entry{ category = "discovery", filename = "zoneTrans.nse" }
Entry{ category = "default", filename = "ftpbounce.nse" }
Entry{ category = "intrusive", filename = "ftpbounce.nse" }
Entry{ category = "version", filename = "skype_v2-version.nse" }
Entry{ category = "discovery", filename = "promiscuous.nse" }
Entry{ category = "default", filename = "SNMPsysdescr.nse" }
Entry{ category = "discovery", filename = "SNMPsysdescr.nse" }
Entry{ category = "safe", filename = "SNMPsysdescr.nse" }
Entry{ category = "demo", filename = "showSMTPVersion.nse" }
Entry{ category = "default", filename = "nbstat.nse" }
Entry{ category = "discovery", filename = "nbstat.nse" }
Entry{ category = "safe", filename = "nbstat.nse" }
Entry{ category = "version", filename = "iax2Detect.nse" }
Entry{ category = "default", filename = "rpcinfo.nse" }
Entry{ category = "safe", filename = "rpcinfo.nse" }
Entry{ category = "discovery", filename = "rpcinfo.nse" }
Entry{ category = "default", filename = "HTTP_open_proxy.nse" }
Entry{ category = "discovery", filename = "HTTP_open_proxy.nse" }
Entry{ category = "intrusive", filename = "HTTP_open_proxy.nse" }
Entry{ category = "demo", filename = "daytimeTest.nse" }
Entry{ category = "intrusive", filename = "HTTPpasswd.nse" }
Entry{ category = "vuln", filename = "HTTPpasswd.nse" }
Entry{ category = "demo", filename = "showSSHVersion.nse" }
Entry{ category = "default", filename = "SMTPcommands.nse" }
Entry{ category = "discovery", filename = "SMTPcommands.nse" }
Entry{ category = "safe", filename = "SMTPcommands.nse" }
Entry{ category = "default", filename = "anonFTP.nse" }
Entry{ category = "auth", filename = "anonFTP.nse" }
Entry{ category = "intrusive", filename = "anonFTP.nse" }
Entry{ category = "version", filename = "netbios-smb-os-discovery.nse" }
Entry{ category = "default", filename = "robots.nse" }
Entry{ category = "safe", filename = "robots.nse" }
Entry{ category = "default", filename = "finger.nse" }
Entry{ category = "discovery", filename = "finger.nse" }
Entry{ category = "default", filename = "UPnP-info.nse" }
Entry{ category = "safe", filename = "UPnP-info.nse" }
Entry{ category = "malware", filename = "strangeSMTPport.nse" }
Entry{ category = "default", filename = "ircServerInfo.nse" }
Entry{ category = "discovery", filename = "ircServerInfo.nse" }
Entry{ category = "malware", filename = "ircZombieTest.nse" }
Entry{ category = "discovery", filename = "ripeQuery.nse" }
Entry{ category = "demo", filename = "showHTTPVersion.nse" }
Entry{ category = "version", filename = "PPTPversion.nse" }
Entry{ category = "discovery", filename = "ASN.nse" }
Entry{ category = "intrusive", filename = "brutePOP3.nse" }
Entry{ category = "auth", filename = "brutePOP3.nse" }
Entry{ category = "default", filename = "popcapa.nse" }
Entry{ category = "intrusive", filename = "SNMPcommunitybrute.nse" }
Entry{ category = "auth", filename = "SNMPcommunitybrute.nse" }
Entry{ category = "discovery", filename = "whois.nse" }
Entry{ category = "safe", filename = "whois.nse" }
Entry{ category = "intrusive", filename = "dns-safe-recursion-txid.nse" }
Entry{ category = "intrusive", filename = "dns-safe-recursion-port.nse" }