Nmap Development mailing list archives
[NSE] ASN made more robust and documented - much more to do.
From: jah <jah () zadkiel plus com>
Date: Sat, 16 Aug 2008 04:21:06 +0100
Hi folks, Attached is a bit of an update to the version of asn.nse in the current svn. It was rather prone to failure and now it is less so. Yesterday I began another version using Philip's dns library and system_dns patch and while I was playing I noticed that a DNS response contained two answers (and that the order in which they appear is seemingly random). At first, I thought this was some strangeness, but this turns out not to be quite the case. The answers to a query for the nmap.asn.cymru.com zone are one each from what would be obtained by queries for both origin.asn.cymru and peer.asn.cymru.com zones [1] and this fact prompted me to return to the original version of the script and update the response decoding routine to handle the extra answers. Further, there's a pair of answers for each BGP prefix in which the target resides which can result in four answers (and perhaps more). So once I'd handled the extraction of multiple answers, I changed the output to suit (not perfectly, mind) and the caching and cache checking code as well. Whilst doing all of this, I noted down several challenges to be solved and thus grew the sizeable TODO section in the script comments which you can probably see below. The upshot of all this rambling is that even though this version of the script is better (it's no longer a throw of the dice which determines the answer outputted - they all are), there's a fair bit more to do to make it reliable and more experimentation/testing needed. Your thoughts on any of the TODO items much appreciated. Example Output: Host script results: | ASN: 4 records found. | ASN: 10565 | BGP: 64.13.128.0/18 | Country: US | ASN: 10565 | BGP: 64.13.128.0/21 | Country: US | ASN: 3561 6461 | BGP: 64.13.128.0/21 | Country: US |_ ASN: 174 2914 6461 | BGP: 64.13.128.0/18 | Country: US Regards, jah [1] http://www.team-cymru.org/Services/ip-to-asn.html#dns
id = "ASN" description = "This script performs IP address to Autonomous System Numbers (ASN) lookups. It \ sends DNS TXT queries to the recursive DNS server you provide which in turn \ queries a 3rd party service provided by team-cymru.org using an in-addr.arpa \ style zone set-up especially for Nmap (nmap.asn.cymru.com). The respnses to these \ queries contain both Origin and Peer ASNs and these will be displayed along with \ the BGP Prefix and Country Code. The script caches results to reduce the number \ of queries and should perform a single query for all scanned targets in \ a BGP prefix present in Team Cymru's database. Please be aware that any targets \ for which a query is performed will be revealed to Team Cymru. \n\n\ Usage: \n\n\ nmap <target> --script asn --script-args dns=<recursion_enabled_dns_server>" --- -- @args dns Required. A recursive nameserver. -- -- @output -- Host script results: -- \n| ASN: 4 records found. -- \n| ASN: 38661 | BGP: 122.99.128.0/18 | Country: KR -- \n| ASN: 38661 | BGP: 122.99.144.0/20 | Country: KR -- \n| ASN: 3786 4766 9318 15412 | BGP: 122.99.128.0/18 | Country: KR -- \n|_ ASN: 3786 4766 9318 15412 | BGP: 122.99.144.0/20 | Country: KR --[[ TODO Length of answers - use the length field or the length byte immediately after that field? aka what happens when the length of the answer > 256... Combine origin and peer ASN numbers for each bgp and label accordingly - IF we can determine a difference between an origin answer and a peer answer (is there always only a single ASN in an origin answer? what about when there's only one peer - confirm using origin.asn...?). Can we send multiple questions in a single packet? Reliably? Does cymru provide IPv6 lookups? Will they in the future? Cache records and possibly miss information or do no caching and hammer the dns server? Pointers for same records - troublesome. Ordering of answers - why aren't they consistent? Is it cymru or caching server that reorders? Lots of peers = untidy output. Check for and report NXDOMAIN? Random TXID?? Random src port?? Use better ipOps.lua functions instead of the shit ip_in_net and two_dwords functions. Use dns.lua instead of most of this script! --]] author = "jah, Michael" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery"} runlevel = 1 local comm = require "comm" local ipOps = require "ipOps" if not nmap.registry.asn then nmap.registry.asn = {} nmap.registry.asn.cache = {} end local mutex = nmap.mutex( id ) --- -- This script will run for any non-private IPv4 address only if the required arguments are -- supplied on the Nmap command line. hostrule = function( host ) if not nmap.registry.args.dns then return false end if host.ip:match( ":" ) then return false end return ( not ipOps.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. action = function( host ) mutex "lock" -- get dns server. local dns_server = nmap.registry.args.dns -- check for cached data local in_cache, output = check_cache( host.ip ) local query, data if not in_cache then -- constuct dns query query, data = make_query( host.ip ) -- send dns query local result = send_query( data, dns_server ) -- decode response local status if result then status, output = pcall( decode_result, result, query ) end if not status then output = nil end end mutex "done" if type( output ) ~= "table" or #output == 0 then return nil end -- return result return ("%s records found.\n%s"):format( #output, 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 output = {} for _, cache_entry in ipairs( nmap.registry.asn.cache ) do if ip_in_net( ip, cache_entry.range ) then output[#output+1] = cache_entry.output end end if #output > 0 then return true, output end return false, nil end --- -- Constructs a PTR-like TXT DNS query packet for the supplied IP address. -- Both the entire packet and the Query part are returned. The Query part may then be -- searched for in the DNS response. -- @param ip String representing the target IP address. -- @return String representing the DNS query section. -- @return String packet ready for transmission to a DNS service. function make_query( ip ) local t = {} t[4], t[3], t[2], t[1] = ip:match( "(%d+)%.(%d+)%.(%d+)%.(%d+)" ) local tsoh = labels( t ) local z = { "nmap", "asn", "cymru", "com" } local zone = labels( z ) 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 query, t_id .. dns_std .. query end --- -- Sends the supplied DNS query data to the supplied DNS service by UDP port 53. -- @param dns_query String DNS TXT IN data. -- @param dns_srv String representing an IP address or host name of a recursive DNS server. -- @return String received in response to the DNS query or nil. function send_query( dns_query, dns_srv ) local catch = function() mutex "done" end local try = nmap.new_try( catch ) local options = {} options.proto = "udp" options.lines = 1 options.timeout = 1000 local result, err = try( comm.exchange( dns_srv, 53, dns_query, options ) ) --if not status then error() end return result end --- -- Extracts any DNS answer sections, relating to the supplied DNS query, from the supplied -- DNS response string. The answers are cached in the registry before being returned. -- @param result String DNS response. -- @param query String DNS Query section used to locate relevent answers in the response. -- @return Table containing a printable string for each answer in the response or nil in -- the case of an error. function decode_result( result, query ) -- read result local answers = {} local start, offset = string.find( result, query ) -- extract one or more queries while start do -- From offset+1 we should have: -- C00C pointer - 2 bytes -- Type - 2 bytes -- Class - 2 bytes -- TTL - 4 bytes -- Length - 2 bytes -- Answer - Length bytes -- The first byte of the answer is actually the length of the rest of the answer (Length - 1) what happens if length > 256 offset = offset+1 + 12 local _, len = bin.unpack( "H", string.sub( result, offset, offset ) ) len = tonumber( len, 16 ) offset = offset + 1 -- start of the answer answers[#answers+1] = string.sub( result, offset, offset + len -1 ) offset = offset + len -1 -- another answer? local _, cooc = bin.unpack( "H2", string.sub( result, offset+1, offset+2 ) ) if cooc:lower() ~= "c00c" then break end end if #answers == 0 then return nil end local output = {} for _, answer in ipairs( answers ) do local record = {} fields = { answer:match( ("([^|]*)|" ):rep(3) ) } record.bgp = fields[2]:gsub( "^%s*(.-)%s*$", "%1" ) fields[1] = "ASN: " .. fields[1] fields[2] = "BGP:" .. fields[2] fields[3] = "Country:" .. fields[3] record.output = table.concat( fields, "| " ) output[#output+1] = record.output -- cache result table.insert( nmap.registry.asn.cache, { range = record.bgp, output = record.output } ) end return output end --- -- Given a table of strings, returns a string made up of concateneted labels where each label -- consists of a length value (cast as char) followed by that number of characters. -- @param t Array style Table containing strings. -- @return String. function labels( t ) local ret = "" for _, v in ipairs(t) do ret = ret .. string.char( string.len(v) ) .. v end return ret end --- -- Checks whether the supplied IP address is within the supplied range of IP addresses. -- @param ip String representing an IPv4 address. -- @param net String representing a range of IPv4 addresses in either A-B or CIDR notation. -- @return Boolean True if the supplied ip address falls inside the supplied range, -- otherwise false. 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) end dw_ip = ipOps.todword(ip) if net_lo <= dw_ip and dw_ip <= net_hi then return true end return false end --- -- Calculates the last IP address from the supplied CIDR range and returns the first and last -- IP addresses in that range as DWORDS. -- @param cidr String representing an IPv4 address range in CIDR notation. -- @return Number representing the first IP address in the supplied range. -- @return Number representing the last IP address in the supplied range. function two_dwords(cidr) local patt = "(%d+)[.]*(%d*)[.]*(%d*)[.]*(%d*)[/]+(%d+)" local a, b, c, d, e, lo_net, host a, b, c, d, e = cidr: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 end return ipOps.todword(lo_net), ipOps.todword(lo_net) + 2^host - 1 end
_______________________________________________ Sent through the nmap-dev mailing list http://cgi.insecure.org/mailman/listinfo/nmap-dev Archived at http://SecLists.Org
Current thread:
- [NSE] ASN made more robust and documented - much more to do. jah (Aug 15)
- Re: [NSE] ASN made more robust and documented - much more to do. David Fifield (Aug 29)
- Message not available
- Message not available
- Re: [NSE] ASN made more robust and documented - much more to do. jah (Sep 01)
- Re: [NSE] ASN made more robust and documented - much more to do. David Fifield (Sep 03)
- Re: [NSE] ASN made more robust and documented - much more to do. jah (Sep 03)
- Re: [NSE] ASN made more robust and documented - much more to do. Michael Pattrick (Sep 03)
- Re: [NSE] ASN made more robust and documented - much more to do. David Fifield (Sep 03)
- Re: [NSE] ASN made more robust and documented - much more to do. jah (Sep 03)
- Re: [NSE] ASN made more robust and documented - much more to do. Michael Pattrick (Sep 03)
- Re: [NSE] ASN jah (Sep 05)
- Re: [NSE] ASN David Fifield (Sep 05)
- Message not available
- Re: [NSE] ASN made more robust and documented - much more to do. David Fifield (Aug 29)