From 5d0e095a814d50039a494a55dd120caa831ec2a8 Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Tue, 9 Sep 2025 10:15:31 +0200 Subject: [PATCH] improvement --- dnsrecon.py | 419 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 262 insertions(+), 157 deletions(-) diff --git a/dnsrecon.py b/dnsrecon.py index 3ab5184..166318d 100644 --- a/dnsrecon.py +++ b/dnsrecon.py @@ -77,15 +77,24 @@ class DNSReconTool: if output and not output.startswith("Error:"): for line in output.split('\n'): line = line.strip() - if line and not line.startswith(';'): + if line and not line.startswith(';') and not line.startswith('>>'): + # Split on whitespace, but preserve quoted strings parts = line.split(None, 4) - if len(parts) >= 4: + if len(parts) >= 5: # name, ttl, class, type, data records.append({ - 'name': parts[0], + 'name': parts[0].rstrip('.'), # Remove trailing dot + 'ttl': parts[1], + 'class': parts[2], + 'type': parts[3], + 'data': parts[4] + }) + elif len(parts) == 4: # Sometimes no data field + records.append({ + 'name': parts[0].rstrip('.'), 'ttl': parts[1], 'class': parts[2], 'type': parts[3], - 'data': ' '.join(parts[4:]) if len(parts) > 4 else '' + 'data': '' }) return { @@ -258,6 +267,254 @@ class DNSReconTool: 'message': 'Shodan query failed' } + def _write_dns_section(self, f, title: str, records: List[Dict], data_extractor): + """Helper method to write DNS record sections.""" + if records: + f.write(f"\n{title}:\n") + for record in records: + data = data_extractor(record) + f.write(f" {data}\n") + else: + f.write(f"\n{title}: None found\n") + + def _write_dns_server_comparison(self, f, dns_data: Dict): + """Compare responses from different DNS servers.""" + servers = ['system', '1.1.1.1', '8.8.8.8', '9.9.9.9'] + record_types = ['A', 'AAAA', 'MX', 'NS'] + + discrepancies_found = False + + for record_type in record_types: + if record_type in dns_data: + server_results = {} + for server in servers: + if server in dns_data[record_type]: + records = dns_data[record_type][server].get('records', []) + server_results[server] = set(r.get('data', '') for r in records if r.get('data')) + + # Check for discrepancies + if len(server_results) > 1: + all_results = list(server_results.values()) + if not all(result == all_results[0] for result in all_results): + if not discrepancies_found: + discrepancies_found = True + f.write(f" ⚠️ {record_type} records differ between DNS servers:\n") + for server, records in server_results.items(): + f.write(f" {server}: {', '.join(sorted(records)) if records else 'No records'}\n") + + if not discrepancies_found: + f.write(" ✅ All DNS servers return consistent results\n") + + def create_summary_report(self, results: Dict[str, Any], filename: str) -> None: + """Create comprehensive human-readable summary report.""" + with open(filename, 'w', encoding='utf-8') as f: + f.write(f"DNS Reconnaissance Report\n") + f.write(f"{'='*50}\n") + f.write(f"Domain: {results['domain']}\n") + f.write(f"Timestamp: {results['timestamp']}\n\n") + + dns_data = results.get('dns_records', {}) + + # Helper function to get records from system DNS + def get_system_records(record_type): + return dns_data.get(record_type, {}).get('system', {}).get('records', []) + + # A Records (IPv4) + self._write_dns_section(f, "A Records (IPv4)", get_system_records('A'), + lambda r: r.get('data', 'N/A')) + + # AAAA Records (IPv6) + self._write_dns_section(f, "AAAA Records (IPv6)", get_system_records('AAAA'), + lambda r: r.get('data', 'N/A')) + + # MX Records (Mail Servers) + mx_records = get_system_records('MX') + if mx_records: + f.write(f"\nMX Records (Mail Servers):\n") + for record in mx_records: + data_parts = record.get('data', '').split() + priority = data_parts[0] if data_parts else 'N/A' + server = ' '.join(data_parts[1:]) if len(data_parts) > 1 else 'N/A' + f.write(f" Priority {priority}: {server}\n") + else: + f.write(f"\nMX Records (Mail Servers): None found\n") + + # NS Records (Name Servers) + self._write_dns_section(f, "NS Records (Name Servers)", get_system_records('NS'), + lambda r: r.get('data', 'N/A')) + + # CNAME Records + self._write_dns_section(f, "CNAME Records", get_system_records('CNAME'), + lambda r: f"{r.get('name', 'N/A')} -> {r.get('data', 'N/A')}") + + # TXT Records + txt_records = get_system_records('TXT') + if txt_records: + f.write(f"\nTXT Records:\n") + for record in txt_records: + txt_data = record.get('data', '').strip() + # Clean up quoted text + if txt_data.startswith('"') and txt_data.endswith('"'): + txt_data = txt_data[1:-1] + + # Identify common TXT record types + if txt_data.startswith('v=spf1'): + f.write(f" [SPF] {txt_data}\n") + elif txt_data.startswith('v=DMARC1'): + f.write(f" [DMARC] {txt_data}\n") + elif txt_data.startswith('v=DKIM1'): + f.write(f" [DKIM] {txt_data}\n") + elif 'google-site-verification' in txt_data: + f.write(f" [Google Verification] {txt_data[:50]}...\n") + else: + f.write(f" {txt_data}\n") + else: + f.write(f"\nTXT Records: None found\n") + + # CAA Records (Certificate Authority Authorization) + caa_records = get_system_records('CAA') + if caa_records: + f.write(f"\nCAA Records (Certificate Authority Authorization):\n") + for record in caa_records: + data_parts = record.get('data', '').split() + if len(data_parts) >= 3: + flags = data_parts[0] + tag = data_parts[1] + value = ' '.join(data_parts[2:]).strip('"') + f.write(f" {flags} {tag} {value}\n") + else: + f.write(f" {record.get('data', 'N/A')}\n") + else: + f.write(f"\nCAA Records: None found\n") + + # SRV Records + srv_records = get_system_records('SRV') + if srv_records: + f.write(f"\nSRV Records (Service Records):\n") + for record in srv_records: + data_parts = record.get('data', '').split() + if len(data_parts) >= 4: + priority, weight, port, target = data_parts[:4] + f.write(f" {record.get('name', 'N/A')}\n") + f.write(f" Priority: {priority}, Weight: {weight}\n") + f.write(f" Port: {port}, Target: {target}\n") + else: + f.write(f" {record.get('data', 'N/A')}\n") + else: + f.write(f"\nSRV Records: None found\n") + + # SOA Record (Start of Authority) + soa_records = get_system_records('SOA') + if soa_records: + f.write(f"\nSOA Record (Zone Authority):\n") + for record in soa_records: + data_parts = record.get('data', '').split() + if len(data_parts) >= 7: + primary_ns, admin_email = data_parts[:2] + serial, refresh, retry, expire, minimum = data_parts[2:7] + f.write(f" Primary Name Server: {primary_ns}\n") + f.write(f" Admin Email: {admin_email}\n") + f.write(f" Serial: {serial}\n") + f.write(f" Refresh: {refresh}s, Retry: {retry}s\n") + f.write(f" Expire: {expire}s, Minimum TTL: {minimum}s\n") + else: + f.write(f" {record.get('data', 'N/A')}\n") + else: + f.write(f"\nSOA Record: None found\n") + + # DNSSEC Information + dnssec_data = dns_data.get('DNSSEC', {}).get('system', {}) + dnssec_output = dnssec_data.get('raw_output', '') + if dnssec_output and not dnssec_output.startswith('Error:') and dnssec_output.strip(): + f.write(f"\nDNSSEC Status:\n") + f.write("-" * 14 + "\n") + if 'RRSIG' in dnssec_output or 'DNSKEY' in dnssec_output: + f.write(f" ✅ DNSSEC is enabled\n") + else: + f.write(f" ❌ DNSSEC not detected\n") + else: + f.write(f"\nDNSSEC Status: Unable to determine\n") + + # DNS Server Comparison (show discrepancies) + f.write(f"\nDNS Server Comparison:\n") + f.write("-" * 21 + "\n") + self._write_dns_server_comparison(f, dns_data) + + # Enhanced WHOIS Information + whois_data = results.get('whois', {}) + if whois_data.get('parsed'): + f.write(f"\nWHOIS Information\n") + f.write("-" * 17 + "\n") + parsed = whois_data['parsed'] + + # Domain info + for field in ['domain_name', 'domain']: + if field in parsed: + f.write(f"Domain: {parsed[field]}\n") + break + + # Registrar info + for field in ['registrar', 'sponsoring_registrar']: + if field in parsed: + f.write(f"Registrar: {parsed[field]}\n") + break + + # Important dates + for field in ['creation_date', 'created']: + if field in parsed: + f.write(f"Created: {parsed[field]}\n") + break + + for field in ['expiration_date', 'registry_expiry_date', 'expires']: + if field in parsed: + f.write(f"Expires: {parsed[field]}\n") + break + + for field in ['updated_date', 'changed', 'last_updated']: + if field in parsed: + f.write(f"Last Updated: {parsed[field]}\n") + break + + # Status + for field in ['status', 'domain_status']: + if field in parsed: + f.write(f"Status: {parsed[field]}\n") + break + + # Name servers from WHOIS + ns_fields = [k for k in parsed.keys() if 'name_server' in k.lower() or k.lower().startswith('nserver')] + if ns_fields: + f.write(f"WHOIS Name Servers:\n") + for field in sorted(ns_fields)[:10]: # Limit to 10 + f.write(f" {parsed[field]}\n") + + # Certificate transparency section + cert_data = results.get('certificate_transparency', {}) + if cert_data.get('success'): + subdomain_count = cert_data.get('subdomain_count', 0) + f.write(f"\nSubdomains from Certificate Logs ({subdomain_count} total):\n") + f.write("-" * 45 + "\n") + subdomains = cert_data.get('unique_subdomains', []) + + display_count = min(50, len(subdomains)) + for subdomain in subdomains[:display_count]: + f.write(f" {subdomain}\n") + + if len(subdomains) > display_count: + f.write(f" ... and {len(subdomains) - display_count} more\n") + + # Shodan section + shodan_data = results.get('shodan', {}) + if shodan_data.get('success') and shodan_data.get('total_results', 0) > 0: + f.write(f"\nShodan Results ({shodan_data.get('total_results', 0)} total):\n") + f.write("-" * 25 + "\n") + for match in shodan_data.get('matches', [])[:5]: + f.write(f" IP: {match.get('ip_str', 'N/A')}\n") + f.write(f" Port: {match.get('port', 'N/A')}\n") + f.write(f" Service: {match.get('product', 'N/A')}\n") + f.write(f" Organization: {match.get('org', 'N/A')}\n") + f.write(" ---\n") + def save_results(self, domain: str, results: Dict[str, Any]) -> None: """Save results in multiple formats.""" if not os.path.exists(self.output_dir): @@ -278,159 +535,7 @@ class DNSReconTool: print(f"\n📄 Results saved:") print(f" JSON: {json_file}") print(f" Summary: {txt_file}") - - def create_summary_report(self, results: Dict[str, Any], filename: str) -> None: - """Create human-readable summary report.""" - with open(filename, 'w', encoding='utf-8') as f: - f.write(f"DNS Reconnaissance Report\n") - f.write(f"{'='*50}\n") - f.write(f"Domain: {results['domain']}\n") - f.write(f"Timestamp: {results['timestamp']}\n\n") - - # DNS Summary - improved parsing - f.write("DNS Records Summary\n") - f.write("-" * 20 + "\n") - - dns_data = results.get('dns_records', {}) - - # A Records - if 'A' in dns_data: - system_records = dns_data['A'].get('system', {}).get('records', []) - if system_records: - f.write(f"\nA Records (IPv4):\n") - for record in system_records: - ip = record.get('data') or record.get('type', 'N/A') - f.write(f" {ip}\n") - else: - f.write(f"\nA Records (IPv4): None found\n") - - # AAAA Records - if 'AAAA' in dns_data: - system_records = dns_data['AAAA'].get('system', {}).get('records', []) - if system_records: - f.write(f"\nAAAA Records (IPv6):\n") - for record in system_records: - ipv6 = record.get('data') or record.get('type', 'N/A') - f.write(f" {ipv6}\n") - else: - f.write(f"\nAAAA Records (IPv6): None found\n") - - # MX Records - if 'MX' in dns_data: - system_records = dns_data['MX'].get('system', {}).get('records', []) - if system_records: - f.write(f"\nMX Records (Mail Servers):\n") - for record in system_records: - priority = record.get('type', '') - server = record.get('data', 'N/A') - f.write(f" Priority {priority}: {server}\n") - else: - f.write(f"\nMX Records (Mail Servers): None found\n") - - # NS Records - if 'NS' in dns_data: - system_records = dns_data['NS'].get('system', {}).get('records', []) - if system_records: - f.write(f"\nNS Records (Name Servers):\n") - for record in system_records: - ns = record.get('data') or record.get('type', 'N/A') - f.write(f" {ns}\n") - else: - f.write(f"\nNS Records (Name Servers): None found\n") - - # TXT Records - if 'TXT' in dns_data: - system_records = dns_data['TXT'].get('system', {}).get('records', []) - if system_records: - f.write(f"\nTXT Records:\n") - for record in system_records: - txt_data = record.get('data', '') - txt_type = record.get('type', '') - full_txt = f"{txt_type} {txt_data}".strip() - if full_txt.startswith('"') and full_txt.endswith('"'): - full_txt = full_txt[1:-1] # Remove quotes - f.write(f" {full_txt}\n") - else: - f.write(f"\nTXT Records: None found\n") - - # SOA Record - if 'SOA' in dns_data: - system_records = dns_data['SOA'].get('system', {}).get('records', []) - if system_records: - f.write(f"\nSOA Record (Zone Authority):\n") - for record in system_records: - primary_ns = record.get('type', 'N/A') - soa_data = record.get('data', 'N/A') - f.write(f" Primary NS: {primary_ns}\n") - f.write(f" Details: {soa_data}\n") - else: - f.write(f"\nSOA Record: None found\n") - - # WHOIS Summary - whois_data = results.get('whois', {}) - if whois_data.get('parsed'): - f.write(f"\nWHOIS Information\n") - f.write("-" * 17 + "\n") - parsed = whois_data['parsed'] - if 'domain' in parsed: - f.write(f"Domain: {parsed['domain']}\n") - if 'changed' in parsed: - f.write(f"Last Updated: {parsed['changed']}\n") - if 'status' in parsed: - f.write(f"Status: {parsed['status']}\n") - - # Certificate Transparency - show more subdomains - cert_data = results.get('certificate_transparency', {}) - if cert_data.get('success'): - subdomain_count = cert_data.get('subdomain_count', 0) - f.write(f"\nSubdomains from Certificate Logs ({subdomain_count} total):\n") - f.write("-" * 45 + "\n") - subdomains = cert_data.get('unique_subdomains', []) - - # Show more subdomains, not just 20 - display_count = min(50, len(subdomains)) # Show up to 50 - for subdomain in subdomains[:display_count]: - f.write(f" {subdomain}\n") - - if len(subdomains) > display_count: - f.write(f" ... and {len(subdomains) - display_count} more\n") - - # Show recent certificates - certs = cert_data.get('certificates', []) - if certs: - f.write(f"\nRecent SSL Certificates:\n") - f.write("-" * 23 + "\n") - for cert in certs[:5]: # Show first 5 certificates - f.write(f" {cert.get('common_name', 'N/A')}\n") - f.write(f" Issuer: {cert.get('issuer', 'N/A')}\n") - f.write(f" Valid: {cert.get('not_before', 'N/A')} to {cert.get('not_after', 'N/A')}\n\n") - - # Shodan Summary - more detailed - shodan_data = results.get('shodan', {}) - if shodan_data.get('success') and shodan_data.get('total_results', 0) > 0: - f.write(f"Shodan Results ({shodan_data.get('total_results', 0)} total):\n") - f.write("-" * 25 + "\n") - for match in shodan_data.get('matches', [])[:10]: # Show up to 10 matches - f.write(f" IP: {match.get('ip_str', 'N/A')}\n") - f.write(f" Port: {match.get('port', 'N/A')}\n") - f.write(f" Transport: {match.get('transport', 'N/A')}\n") - f.write(f" ISP: {match.get('isp', 'N/A')}\n") - f.write(f" Organization: {match.get('org', 'N/A')}\n") - f.write(f" Location: {match.get('location', {}).get('city', 'N/A')}, {match.get('location', {}).get('country_name', 'N/A')}\n") - - # SSL info if available - if 'ssl' in match and match['ssl'].get('cert'): - cert = match['ssl']['cert'] - f.write(f" SSL Subject: {cert.get('subject', {}).get('CN', 'N/A')}\n") - f.write(f" SSL Expires: {cert.get('expires', 'N/A')}\n") - - f.write(f" ---\n") - else: - f.write(f"\nShodan Results: No data available\n") - if not shodan_data.get('success'): - error_msg = shodan_data.get('message', 'Unknown error') - f.write(f" Error: {error_msg}\n") - + def run_reconnaissance(self, domain: str) -> Dict[str, Any]: """Run complete DNS reconnaissance.""" print(f"\n🚀 Starting DNS reconnaissance for: {domain}")