From b89667a9a2e7ac38321fab417a801cf844f1f2eb Mon Sep 17 00:00:00 2001 From: lanjelot Date: Fri, 29 Jun 2012 11:37:38 +1000 Subject: [PATCH] improved dns modules, swithed to dnspython --- patator.py | 377 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 211 insertions(+), 166 deletions(-) diff --git a/patator.py b/patator.py index cceaa63..ebc4a8c 100755 --- a/patator.py +++ b/patator.py @@ -48,8 +48,8 @@ Currently it supports the following modules: - pgsql_login : Brute-force PostgreSQL - vnc_login : Brute-force VNC - - dns_forward : Forward lookup subdomains - - dns_reverse : Reverse lookup subnets + - dns_forward : Brute-force DNS + - dns_reverse : Brute-force DNS (reverse lookup subnets) - snmp_login : Brute-force SNMPv1/2 and SNMPv3 - unzip_pass : Brute-force the password of encrypted ZIP files @@ -128,7 +128,7 @@ psycopg | PostgreSQL | http://initd.org/psycopg/ -------------------------------------------------------------------------------------------------- pycrypto | VNC | http://www.dlitz.net/software/pycrypto/ | 2.3 | -------------------------------------------------------------------------------------------------- -pydns | DNS | http://pydns.sourceforge.net/ | 2.3.4 | +dnspython | DNS | http://www.dnspython.org/ | 1.10.0 | -------------------------------------------------------------------------------------------------- pysnmp | SNMP | http://pysnmp.sourceforge.net/ | 4.2.1 | -------------------------------------------------------------------------------------------------- @@ -494,25 +494,29 @@ unzip_pass zipfile=path/to/file.zip password=FILE0 0=passwords.txt -x ignore:cod }}} {{{ DNS -* Forward lookup subdomains. +* Brute-force subdomains. (a) Ignore NXDOMAIN responses (rcode 3). ----------- -dns_forward domain=FILE0.google.com 0=names.txt -x ignore:code=3 - (a) -* Forward lookup domain with all possible TLDs. +dns_forward name=FILE0.google.com 0=names.txt -x ignore:code=3 + (a) +* Brute-force domain with every possible TLDs. ----------- -dns_forward domain=google.MOD0 0=TLD -x ignore:code=3 +dns_forward name=google.MOD0 0=TLD -x ignore:code=3 -* Foward lookup SRV records. +* Brute-force SRV records. ----------- -dns_forward domain=MOD0.microsoft.com 0=SRV qtype=SRV -x ignore:code=3 +dns_forward name=MOD0.microsoft.com 0=SRV qtype=SRV -x ignore:code=3 -* Reverse lookup several subnets. +* Grab the version of several hosts. +----------- +dns_forward server=FILE0 0=hosts.txt name=version.bind qtype=txt qclass=ch + +* Reverse lookup several networks. (a) Ignore names that do not contain 'google.com'. (b) Ignore generic PTR records. ----------- dns_reverse host=NET0 0=216.239.32.0-216.239.47.255,8.8.8.0/24 -x ignore:code=3 -x ignore:fgrep!=google.com -x ignore:fgrep=216-239- - (a) (b) + (a) (b) }}} {{{ SNMP @@ -554,7 +558,7 @@ TODO ---- * SSL support for SMTP, MySQL, ... (use socat in the meantime) * new option -e ns like in Medusa (not likely to be implemented due to design) - * replace PyDNS|paramiko|IPy with a better module (scapy|libssh2|... ?) + * replace dnspython|paramiko|IPy with a better module (scapy|libssh2|... ?) * rewrite itertools.product that eats too much memory when processing large wordlists ''' @@ -585,6 +589,7 @@ from struct import unpack import socket import subprocess import hashlib +from collections import defaultdict try: # python3+ from queue import Queue, Empty, Full @@ -2737,6 +2742,72 @@ class VNC_login: # }}} # DNS {{{ + +try: + import dns.rdatatype + import dns.message + import dns.query + import dns.reversename +except ImportError: + warnings.append('dnspython') + +def dns_query(server, timeout, protocol, qname, qtype, qclass): + request = dns.message.make_query(qname, qtype, qclass) + + if protocol == 'tcp': + response = dns.query.tcp(request, server, timeout=timeout, one_rr_per_rrset=True) + + else: + response = dns.query.udp(request, server, timeout=timeout, one_rr_per_rrset=True) + + if response.flags & dns.flags.TC: + response = dns.query.tcp(request, server, timeout=timeout, one_rr_per_rrset=True) + + return response + +def generate_tld(): + gtld = [ + 'aero', 'arpa', 'asia', 'biz', 'cat', 'com', 'coop', 'edu', + 'gov', 'info', 'int', 'jobs', 'mil', 'mobi', 'museum', 'name', + 'net', 'org', 'pro', 'tel', 'travel'] + + cctld = [''.join(i) for i in product(*[ascii_lowercase]*2)] + tld = gtld + cctld + return tld, len(tld) + +def generate_srv(): + common = [ + '_gc._tcp', '_kerberos._tcp', '_kerberos._udp', '_ldap._tcp', + '_test._tcp', '_sips._tcp', '_sip._udp', '_sip._tcp', '_aix._tcp', '_aix._udp', + '_finger._tcp', '_ftp._tcp', '_http._tcp', '_nntp._tcp', '_telnet._tcp', + '_whois._tcp', '_h323cs._tcp', '_h323cs._udp', '_h323be._tcp', '_h323be._udp', + '_h323ls._tcp', '_h323ls._udp', '_sipinternal._tcp', '_sipinternaltls._tcp', + '_sip._tls', '_sipfederationtls._tcp', '_jabber._tcp', '_xmpp-server._tcp', '_xmpp-client._tcp', + '_imap.tcp', '_certificates._tcp', '_crls._tcp', '_pgpkeys._tcp', '_pgprevokations._tcp', + '_cmp._tcp', '_svcp._tcp', '_crl._tcp', '_ocsp._tcp', '_PKIXREP._tcp', + '_smtp._tcp', '_hkp._tcp', '_hkps._tcp', '_jabber._udp', '_xmpp-server._udp', + '_xmpp-client._udp', '_jabber-client._tcp', '_jabber-client._udp', + '_adsp._domainkey', '_policy._domainkey', '_domainkey', '_ldap._tcp.dc._msdcs', '_ldap._udp.dc._msdcs'] + + def distro(): + import os + import re + files = ['/usr/share/nmap/nmap-protocols', '/usr/share/nmap/nmap-services', '/etc/protocols', '/etc/services'] + ret = [] + for f in files: + if not os.path.isfile(f): + logger.warn("File '%s' is missing, there will be less records to test" % f) + continue + for line in open(f): + match = re.match(r'([a-zA-Z0-9]+)\s', line) + if not match: continue + for w in re.split(r'[^a-z0-9]', match.group(1).strip().lower()): + ret.extend(['_%s.%s' % (w, i) for i in ('_tcp', '_udp')]) + return ret + + srv = set(common + distro()) + return srv, len(srv) + class HostInfo: def __init__(self): self.name = set() @@ -2757,61 +2828,99 @@ class HostInfo: return line class Controller_DNS(Controller): - hostmap = {} + records = defaultdict(list) + hostmap = defaultdict(HostInfo) # show_final {{{ def show_final(self): + ''' Expected output: + Records ----- + ftp.example.com. IN A 10.0.1.1 + www.example.com. IN A 10.0.1.1 + prod.example.com. IN CNAME www.example.com. + ipv6.example.com. IN AAAA dead:beef:: + dev.example.com. IN A 10.0.1.2 + svn.example.com. IN A 10.0.2.1 + websrv1.example.com. IN CNAME prod.example.com. + blog.example.com. IN CNAME example.wordpress.com. ''' - 1.2.3.4 ftp.example.com - . www.example.com - . www2.example.com - noip cms.example.com -> www.mistake.com + print('Records ' + '-'*42) + for name, infos in sorted(self.records.items()): + for qclass, qtype, rdata in infos: + print('%34s %8s %-8s %s' % (name, qclass, qtype, rdata)) + + ''' Expected output: + Hostmap ------ + ipv6.example.com dead:beef:: + ftp.example.com 10.0.1.1 + www.example.com 10.0.1.1 + prod.example.com + websrv1.example.com + dev.example.com 10.0.1.2 + svn.example.com 10.0.2.1 + example.wordpress.com ? + blog.example.com + Domains --------------------------- + example.com 8 + Networks -------------------------- + dead:beef:: + 10.0.1.x + 10.0.2.1 ''' - ipmap = {} - noips = set() + ipmap = defaultdict(HostInfo) + noips = defaultdict(list) ''' hostmap = { - 'ftp.example.com': {'ip': ['1.2.3.4'], 'alias': []}, - 'www.example.com': {'ip': ['1.2.3.4'], 'alias': ['www2.example.com']}, - 'www.mistake.com': {'ip': [], 'alias': ['cms.example.com']}, ...} - ipmap = {'1.2.3.4': {'name': ['www.example.com', 'ftp.example.com'], 'alias': ('www2.example.com')}} - noips = ['cms.example.com -> www.mistake.com', ...] + 'www.example.com': {'ip': ['10.0.1.1'], 'alias': ['prod.example.com']}, + 'ftp.example.com': {'ip': ['10.0.1.1'], 'alias': []}, + 'prod.example.com': {'ip': [], 'alias': ['websrv1.example.com']}, + 'ipv6.example.com': {'ip': ['dead:beef::'], 'alias': []}, + 'dev.example.com': {'ip': ['10.0.1.2'], 'alias': []}, + 'example.wordpress.com': {'ip': [], 'alias': ['blog.example.com']}, + + ipmap = {'10.0.1.1': {'name': ['www.example.com', 'ftp.example.com'], 'alias': ['prod.example.com', 'websrv1.example.com']}, ... + noips = {'example.wordpress.com': ['blog.example.com'], ''' + for name, hinfo in self.hostmap.items(): - logger.debug('%s -> %s' % (name, hinfo)) - if not hinfo.ip: # orphan CNAME hostnames (with no IP address) may be still valid virtual hosts - for alias in hinfo.alias: - noips.add('%s -> %s' % (alias, name)) - else: - for ip in hinfo.ip: - if ip not in ipmap: ipmap[ip] = HostInfo() - ipmap[ip].name.add(name) - ipmap[ip].alias.update(hinfo.alias) - - # pretty print - def pprint_info(key, infos): - first = True - for info in infos: - if first: - print('%34s %s' % (info, key)) - first = False - else: - print('%34s %s' % (info, key)) - + for ip in hinfo.ip: + ip = IP(ip) + ipmap[ip].name.add(name) + ipmap[ip].alias.update(hinfo.alias) + + for name, hinfo in self.hostmap.items(): + if not hinfo.ip and hinfo.alias: + found = False + for ip, v in ipmap.items(): + if name in v.alias: + for alias in hinfo.alias: + ipmap[ip].alias.add(alias) + found = True + + if not found: # orphan CNAME hostnames (with no IP address) may be still valid virtual hosts + noips[name].extend(hinfo.alias) + print('Hostmap ' + '-'*42) for ip, hinfo in sorted(ipmap.items()): - pprint_info( ip, hinfo.name) - pprint_info('.', hinfo.alias) - - pprint_info('noip', noips) + for name in hinfo.name: + print('%34s %s' % (name, ip)) + for alias in hinfo.alias: + print('%34s' % alias) + + for k, v in noips.items(): + print('%34s ?' % k) + for alias in v: + print('%34s' % alias) print('Domains ' + '-'*42) domains = {} - networks = {} for ip, hinfo in ipmap.items(): - for name in hinfo.name: - i = 1 if name.count('.') > 1 else 0 + for name in hinfo.name.union(hinfo.alias): + if name.count('.') > 1: + i = 1 + else: + i = 0 d = '.'.join(name.split('.')[i:]) if d not in domains: domains[d] = 0 domains[d] += 1 @@ -2831,67 +2940,37 @@ class Controller_DNS(Controller): for net, ips in sorted(nets.items()): if len(ips) == 1: - print(' '*10 + '%39s' % ips[0]) + print(' '*34 + ' %s' % ips[0]) else: - print(' '*10 + '%37s.x' % '.'.join(str(net).split('.')[:-1])) + print(' '*34 + ' %s.x' % '.'.join(str(net).split('.')[:-1])) # }}} def push_final(self, resp): - for name, hinfo in resp.hostmap.items(): - if name not in self.hostmap: - self.hostmap[name] = hinfo - else: - self.hostmap[name].ip.update(hinfo.ip) - self.hostmap[name].alias.update(hinfo.alias) + if hasattr(resp, 'rrs'): + for rr in resp.rrs: + name, qclass, qtype, data = rr -def generate_tld(): - gtld = [ - 'aero', 'arpa', 'asia', 'biz', 'cat', 'com', 'coop', 'edu', - 'gov', 'info', 'int', 'jobs', 'mil', 'mobi', 'museum', 'name', - 'net', 'org', 'pro', 'tel', 'travel'] + info = (qclass, qtype, data) + if info not in self.records[name]: + self.records[name].append(info) - cctld = [''.join(i) for i in product(*[ascii_lowercase]*2)] - tld = gtld + cctld - return tld, len(tld) + if not qclass == 'IN': + continue -def generate_srv(): - common = [ - '_gc._tcp', '_kerberos._tcp', '_kerberos._udp', '_ldap._tcp', - '_test._tcp', '_sips._tcp', '_sip._udp', '_sip._tcp', '_aix._tcp', '_aix._udp', - '_finger._tcp', '_ftp._tcp', '_http._tcp', '_nntp._tcp', '_telnet._tcp', - '_whois._tcp', '_h323cs._tcp', '_h323cs._udp', '_h323be._tcp', '_h323be._udp', - '_h323ls._tcp', '_h323ls._udp', '_sipinternal._tcp', '_sipinternaltls._tcp', - '_sip._tls', '_sipfederationtls._tcp', '_jabber._tcp', '_xmpp-server._tcp', '_xmpp-client._tcp', - '_imap.tcp', '_certificates._tcp', '_crls._tcp', '_pgpkeys._tcp', '_pgprevokations._tcp', - '_cmp._tcp', '_svcp._tcp', '_crl._tcp', '_ocsp._tcp', '_PKIXREP._tcp', - '_smtp._tcp', '_hkp._tcp', '_hkps._tcp', '_jabber._udp', '_xmpp-server._udp', - '_xmpp-client._udp', '_jabber-client._tcp', '_jabber-client._udp', - '_adsp._domainkey', '_policy._domainkey', '_domainkey', '_ldap._tcp.dc._msdcs', '_ldap._udp.dc._msdcs'] + if qtype == 'PTR': + data = data[:-1] + self.hostmap[data].ip.add(name) - def distro(): - import os - import re - files = ['/usr/share/nmap/nmap-protocols', '/usr/share/nmap/nmap-services', '/etc/protocols', '/etc/services'] - ret = [] - for f in files: - if not os.path.isfile(f): - logger.warn("File '%s' is missing, there will be less records to test" % f) - continue - for line in open(f): - match = re.match(r'([a-zA-Z0-9]+)\s', line) - if not match: continue - for w in re.split(r'[^a-z0-9]', match.group(1).strip().lower()): - ret.extend(['_%s.%s' % (w, i) for i in ('_tcp', '_udp')]) - return ret + else: + if qtype in ('A', 'AAAA'): + name = name[:-1] + self.hostmap[name].ip.add(data) - srv = set(common + distro()) - return srv, len(srv) + elif qtype == 'CNAME': + name, data = name[:-1], data[:-1] + self.hostmap[data].alias.add(name) -try: - from DNS import DnsRequest, DNSError -except ImportError: - warnings.append('pydns') class DNS_reverse: '''Reverse lookup subnets''' @@ -2902,50 +2981,46 @@ class DNS_reverse: ] available_options = ( - ('host', 'IP addresses to reverse'), + ('host', 'IP addresses to reverse lookup'), ('server', 'name server to query (directly asking a zone authoritative NS may return more results) [8.8.8.8]'), - ('timeout', 'seconds to wait for a DNS response [10]'), + ('timeout', 'seconds to wait for a DNS response [5]'), + ('protocol', 'send queries over udp or tcp [udp]'), ) available_actions = () Response = Response_Base - def execute(self, host, server='8.8.8.8', timeout='10'): - resolver = DnsRequest(qtype='PTR', server=server, timeout=int(timeout)) - - ip = IP(host) - ptr = ip.reverseName() - result = resolver.req(ptr.rstrip('.')) - hostnames = [ans['data'] for ans in result.answers] + def execute(self, host, server='8.8.8.8', timeout='5', protocol='udp'): - hostmap = {} - for n in hostnames: - if n not in hostmap: hostmap[n] = HostInfo() - hostmap[n].ip.add(ip) + response = dns_query(server, int(timeout), protocol, dns.reversename.from_address(host), qtype='PTR', qclass='IN') - code = result.header['rcode'] - status = result.header['status'] - mesg = '%s %s' % (status, ', '.join(hostnames)) + code = response.rcode() + status = dns.rcode.to_text(code) + rrs = [[host, c, t, d] for _, _, c, t, d in [rr.to_text().split(' ', 4) for rr in response.answer]] + mesg = '%s %s' % (status, ''.join('[%s]' % ' '.join(rr) for rr in rrs)) resp = self.Response(code, mesg) - resp.hostmap = hostmap + + resp.rrs = rrs return resp class DNS_forward: - '''Forward lookup subdomains''' + '''Forward lookup names''' usage_hints = [ - """%prog domain=FILE0.google.com 0=names.txt -x ignore:code=3""", - """%prog domain=google.MOD0 0=TLD -x ignore:code=3""", - """%prog domain=MOD0.microsoft.com 0=SRV qtype=SRV -x ignore:code=3""", + """%prog name=FILE0.google.com 0=names.txt -x ignore:code=3""", + """%prog name=google.MOD0 0=TLD -x ignore:code=3""", + """%prog name=MOD0.microsoft.com 0=SRV qtype=SRV -x ignore:code=3""", ] available_options = ( - ('domain', 'domains to lookup'), + ('name', 'domain names to lookup'), ('server', 'name server to query (directly asking the zone authoritative NS may return more results) [8.8.8.8]'), - ('timeout', 'seconds to wait for a DNS response [10]'), - ('qtype', 'comma-separated list of types to query [ANY,A,AAAA]'), + ('timeout', 'seconds to wait for a DNS response [5]'), + ('protocol', 'send queries over udp or tcp [udp]'), + ('qtype', 'type to query [ANY]'), + ('qclass', 'class to query [IN]'), ) available_actions = () @@ -2956,48 +3031,18 @@ class DNS_forward: Response = Response_Base - def execute(self, domain, server='8.8.8.8', timeout='10', qtype='ANY,A,AAAA'): - resolver = DnsRequest(server=server, timeout=int(timeout)) - - hostmap = {} - for qt in qtype.split(','): - result = resolver.req(domain, qtype=qt.strip()) + def execute(self, name, server='8.8.8.8', timeout='5', protocol='udp', qtype='ANY', qclass='IN'): - for r in result.answers + result.additional + result.authority: - t = r['typename'] - n = r['name'] - d = r['data'] - - if t not in ('A', 'AAAA', 'CNAME', 'DNAME', 'SRV'): - continue - - if t == 'SRV': - _, _, _, d = d - - if t in ('CNAME', 'DNAME', 'SRV'): - n, d = d, n - - if n not in hostmap: - hostmap[n] = HostInfo() + response = dns_query(server, int(timeout), protocol, name, qtype=qtype, qclass=qclass) - if t == 'A': - hostmap[n].ip.add(IP(d)) - - elif t == 'AAAA': - hostmap[n].ip.add(IP(hexlify(d))) - - elif t in ('CNAME', 'DNAME'): - hostmap[n].alias.add(d) - - elif t == 'SRV': - hostmap[n].alias.add(d) - - code = result.header['rcode'] - status = result.header['status'] - mesg = '%s %s' % (status, ' | '.join('%s / %s' % (k, v) for k, v in hostmap.items())) + code = response.rcode() + status = dns.rcode.to_text(code) + rrs = [[n, c, t, d] for n, _, c, t, d in [rr.to_text().split(' ', 4) for rr in response.answer + response.additional + response.authority]] + mesg = '%s %s' % (status, ''.join('[%s]' % ' '.join(rr) for rr in rrs)) resp = self.Response(code, mesg) - resp.hostmap = hostmap + + resp.rrs = rrs return resp @@ -3156,8 +3201,8 @@ modules = [ ('pgsql_login', (Controller, Pgsql_login)), ('vnc_login', (Controller, VNC_login)), - ('dns_reverse', (Controller_DNS, DNS_reverse)), ('dns_forward', (Controller_DNS, DNS_forward)), + ('dns_reverse', (Controller_DNS, DNS_reverse)), ('snmp_login', (Controller, SNMP_login)), ('unzip_pass', (Controller, Unzip_pass)), @@ -3173,7 +3218,7 @@ dependencies = { 'mysql-python': [('mysql_login',), 'http://sourceforge.net/projects/mysql-python/'], 'psycopg': [('pgsql_login',), 'http://initd.org/psycopg/'], 'pycrypto': [('vnc_login',), 'http://www.dlitz.net/software/pycrypto/'], - 'pydns': [('dns_reverse', 'dns_forward'), 'http://pydns.sourceforge.net/'], + 'dnspython': [('dns_reverse', 'dns_forward'), 'http://www.dnspython.org/'], 'IPy': [('dns_reverse', 'dns_forward'), 'https://github.com/haypo/python-ipy'], 'pysnmp': [('snmp_login',), 'http://pysnmp.sf.net/'], 'unzip': [('unzip_pass',), 'http://www.info-zip.org/'],