#!/usr/bin/env python # -*- coding: iso-8859-1 -*- # Dinko Korunic 'kreator', 2006. # opendns.py # script for sweeping the given set of domains in search of open recursion # servers """This program can be used to extract a list of open-recursion nameservers from a given list of domains. Usage is simple: only parameter needed is filename containing list of domains, separated by newlines or whitespace. Program will then get autoritative resolvers for a given domain and check if recursion is enabled. Generally, open recursive resolvers can lead to DNS amplification attacks, DNS cache poisoning attacks, etc. For more information refer to: http://www.icann.org/committees/security/dns-ddos-advisory-31mar06.pdf http://www.merit.edu/mail.archives/nanog/2006-02/msg00579.html http://isotf.org/news/DNS-Amplification-Attacks.pdf """ # History: # 1.0 - Initial release __copyright__ = """Copyright (C) 2005 Dinko Korunic, InfoMAR d.o.o. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ __version__ = '1.0' import sys import DNS import cPickle class memoize(object): """Decorator that caches a function's return value each time it is called. If called later with the same arguments, the cached value is returned, and not re-evaluated. Slow for mutable types. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/466320 """ def __init__(self, func): self.func = func self._cache = {} def __call__(self, *args, **kwds): t = (args, kwds.items()) try: hash(t) key = t except TypeError: try: key = cPickle.dumps(t) except cPickle.PicklingError: return func(*args, **kwds) if not key in self._cache: self._cache[key] = self.func(*args, **kwds) return self._cache[key] def usage(): """Print usage...""" usage_text = """Usage: %s domain_filename Where domain_filename is a newline or space separated list of domain names. Tool will extract list of nameservers for such domain and query them for www.google.com to check if they are open recursive resolvers. """ print usage_text % sys.argv[0] sys.exit(0) def load_domains(domains_filename): """Get the list of domains from a given filename""" domains = {} try: domains_file = file(domains_filename, 'r') words = [] for line in domains_file: words = line.split() for word in words: domains[word] = 1 except IOError, (errno, strerror): print 'I/O error with %s(%s): %s' % \ (domains_filename, errno, strerror) sys.exit(1) return [x for x in domains] @memoize def gethostbyaddr(label): """Get a set of IP addresses for a given label""" try: q = DNS.Request(label, qtype = 'A').req() except: return [] if q.header['status'] != 'NOERROR': return [] return [x['data'] for x in q.answers] @memoize def get_nameserver(domain): """Get a nameserver for a given domain""" try: q = DNS.Request(domain, qtype = 'NS').req() except: return [] if q.header['status'] != 'NOERROR': return [] return [x['data'] for x in q.answers] @memoize def recursive_nameserver(nameserver): """Check if a remote nameserver is recursive""" # check for label label = 'dkorunic.net' try: q = DNS.Request(label, qtype = 'A', server = nameserver, rd = 1).req() except: return False # check if there is no error and RA bit is set if q.header['status'] == 'NOERROR' and q.header['ra'] == 1: return True return False def print_recursives(out_file, nameserver_list): """Iterate the nameserver list and print out the domains and recursive resolvers in that domain """ for domain, servers in nameserver_list: recursives = {} for server in servers: nameserver_ips = gethostbyaddr(server) recursive_ips = [] for nameserver_ip in nameserver_ips: if recursive_nameserver(nameserver_ip): recursive_ips.append(nameserver_ip) if recursive_ips: recursives[server] = recursive_ips if recursives: print >>out_file, domain, recursives def main(argv = None): """Call all necessary stuff...""" # parse CLI arguments or arguments from main() if argv is None: argv = sys.argv # check if proper number of arguments used if len(argv) != 2: usage() # read list of domains domain_list = load_domains(argv[1]) # get list of pairs (domain, (ns1, ns2...)) DNS.ParseResolvConf() nameserver_list = [(x, get_nameserver(x)) for x in domain_list] # print out only the recursive print_recursives(sys.stdout, nameserver_list) if __name__ == '__main__': sys.exit(main())